diff --git a/DEPS b/DEPS
index fc29170..dce639c 100644
--- a/DEPS
+++ b/DEPS
@@ -285,15 +285,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '6270c3445a6500438a36b37ac354725940c98d12',
+  'skia_revision': '5754b81988b80b27fb932d10eaef6e19e949d0be',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'cad9e2824b7ef03cb0eed85eb34aadc5c6d11e59',
+  'v8_revision': 'c272b7f666719df9d92a448af368cc3ef4c30baf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '77d86c4a7ecc62356fde5ddcc83a25e2e51747bd',
+  'angle_revision': '6bae26f6f0f3e61e2fe057d72615f1f6219e79bb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -312,7 +312,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:12.20230420.3.1',
+  'fuchsia_version': 'version:12.20230421.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -372,7 +372,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': 'e25dbdff09521277a4334ae16347de1c884b63c5',
+  'devtools_frontend_revision': '06bb4dadbfdaaca44c1c9f47712d14206fb0e77b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -412,7 +412,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'f9719b7588cce8175147facb6ae90dc4e8af6371',
+  'dawn_revision': 'c421a4b0ce2ba9ff9a238e2116fc457ea33febf0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -456,7 +456,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    'a64df6cce230b9d4931b32eeae44ec1af2b63caa',
+  'libcxxabi_revision':    '64d1adcc575cb29aaac48eb7e8ed04be8504ebf6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -782,7 +782,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '347919bee49bb0aed5f092bdc37707fad5695101',
+    'df891523144cf2bdd37ba0a7bc077c2b9d8e7c6a',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -971,7 +971,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'ca8ma1XtK1oGxFaro9M1bU8jeyZR1YXcKhgdwc8a6gMC',
+          'version': 'SwHpo0FCFofvEZpR__XSzcR2q_dGqrg1jn9rKk_39F0C',
       },
     ],
     'condition': 'checkout_android',
@@ -1212,7 +1212,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '6dca7f0c299f97ca962885ebf5fed0f68141fdb6',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '05992653342396430d9d745ce50c6af0911b5039',
     'condition': 'checkout_src_internal',
   },
 
@@ -1687,7 +1687,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '4f9f0278b3d294d668cae35171e5070be160536a',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '26b08771635aa44efa337d344336008dbcb65cdf',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1872,7 +1872,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'bc8d8ae5463bffc5a106bcaaeb68b1ea12a130dc',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '7c0525b98b26ef3187393b5e227f33f392a89744',
+    Var('webrtc_git') + '/src.git' + '@' + 'ff6cd5330367e067f0435ba462077c52f313c67d',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1962,7 +1962,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@4d7135104ee8f5ca40f7bbca9dc408b4ff40e932',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@34ef198e41f39ff49879f4be54c4c719248e850e',
     'condition': 'checkout_src_internal',
   },
 
@@ -2025,7 +2025,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'PFEX-b7wU-xlJMKma6qPYQPM4JmReHBVtbcq5O5zJjIC',
+        'version': '1ai6JoXMH2OqOfVl73jeWAFmvP2AYkFVji4wl65yIT0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 6f3b72a..02b94315 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -1174,7 +1174,12 @@
         ' char and std::string instead?',
       ),
       True,
-      [_THIRD_PARTY_EXCEPT_BLINK],  # Don't warn in third_party folders.
+      [
+        # The demangler does not use this type but needs to know about it.
+        'base/third_party/symbolize/demangle\.cc',
+        # Don't warn in third_party folders.
+        _THIRD_PARTY_EXCEPT_BLINK
+      ],
     ),
     BanRule(
       r'/(\b(co_await|co_return|co_yield)\b|#include <coroutine>)',
diff --git a/ash/rounded_display/rounded_display_frame_factory_unittest.cc b/ash/rounded_display/rounded_display_frame_factory_unittest.cc
index 2b526f5f..bfa5a2c9 100644
--- a/ash/rounded_display/rounded_display_frame_factory_unittest.cc
+++ b/ash/rounded_display/rounded_display_frame_factory_unittest.cc
@@ -101,17 +101,6 @@
     }
   }
 
-  // Creates non overlay gutters and appends them to `gutters_`.
-  void AppendNonOverlayGutters(const gfx::Size& display_size_in_pixels,
-                               const gfx::RoundedCornersF& panel_radii) {
-    auto non_overlay_gutters = gutter_factory_->CreateNonOverlayGutters(
-        display_size_in_pixels, panel_radii);
-
-    for (auto& gutter : non_overlay_gutters) {
-      gutters_.push_back(std::move(gutter));
-    }
-  }
-
  protected:
   std::unique_ptr<RoundedDisplayGutterFactory> gutter_factory_;
   std::unique_ptr<RoundedDisplayFrameFactory> frame_factory_;
@@ -212,9 +201,9 @@
 TEST_F(RoundedDisplayFrameFactoryTest,
        CorrectRoundedDisplayInfo_GuttersWithOneCorner) {
   const auto panel_radii = gfx::RoundedCornersF(10, 0, 0, 0);
-  AppendNonOverlayGutters(kTestDisplaySize, panel_radii);
+  AppendHorizontalOverlayGutters(kTestDisplaySize, panel_radii);
 
-  // `gutter_factory_` will only create upper-left non-overlay gutter.
+  // `gutter_factory_` will only create upper overlay gutter.
   EXPECT_EQ(gutters_.size(), 1u);
 
   auto frame = frame_factory_->CreateCompositorFrame(
@@ -235,7 +224,6 @@
 
 TEST_F(RoundedDisplayFrameFactoryTest, OnlyCreateNewResourcesWhenNecessary) {
   AppendVerticalOverlayGutters(kTestDisplaySize, kTestPanelRadii);
-  AppendNonOverlayGutters(kTestDisplaySize, kTestPanelRadii);
 
   const auto& gutters = GetGutters();
 
@@ -248,7 +236,7 @@
                                                      /*is_overlay=*/false));
   }
 
-  EXPECT_EQ(resource_manager_.available_resources_count(), 6u);
+  EXPECT_EQ(resource_manager_.available_resources_count(), 2u);
 
   frame_factory_->CreateCompositorFrame(
       viz::BeginFrameAck::CreateManualAckWithDamage(), *host_window_,
@@ -256,13 +244,13 @@
 
   // Should have reused all the resources.
   EXPECT_EQ(resource_manager_.available_resources_count(), 0u);
-  // Should have exported six resources as we have six gutters.
-  EXPECT_EQ(resource_manager_.exported_resources_count(), 6u);
+  // Should have exported two resources as we have two gutters.
+  EXPECT_EQ(resource_manager_.exported_resources_count(), 2u);
 
   resource_manager_.LostExportedResources();
 
   // Adding more resources.
-  for (int index : {0, 0, 4, 5}) {
+  for (int index : {0, 0}) {
     const auto* gutter = gutters.at(index);
     resource_manager_.OfferResource(
         RoundedDisplayFrameFactory::CreateUiResource(gutter->bounds().size(),
@@ -271,7 +259,7 @@
                                                      /*is_overlay=*/false));
   }
 
-  EXPECT_EQ(resource_manager_.available_resources_count(), 4u);
+  EXPECT_EQ(resource_manager_.available_resources_count(), 2u);
 
   frame_factory_->CreateCompositorFrame(
       viz::BeginFrameAck::CreateManualAckWithDamage(), *host_window_,
@@ -279,11 +267,11 @@
 
   // We end up using the available resources and are left with the extra
   // resource that was available. We also must have created resources for
-  // gutters for which we did not have any available resources.
+  // gutter for which we did not have any available resources.
   EXPECT_EQ(resource_manager_.available_resources_count(), 1u);
 
-  // Should have exported six resources as we have six gutters.
-  EXPECT_EQ(resource_manager_.exported_resources_count(), 6u);
+  // Should have exported two resources as we have two gutters.
+  EXPECT_EQ(resource_manager_.exported_resources_count(), 2u);
 }
 
 }  // namespace
diff --git a/ash/rounded_display/rounded_display_gutter_factory.cc b/ash/rounded_display/rounded_display_gutter_factory.cc
index 2c84dec5..7d96a6d6 100644
--- a/ash/rounded_display/rounded_display_gutter_factory.cc
+++ b/ash/rounded_display/rounded_display_gutter_factory.cc
@@ -126,28 +126,4 @@
   return gutters;
 }
 
-std::vector<std::unique_ptr<RoundedDisplayGutter>>
-RoundedDisplayGutterFactory::CreateNonOverlayGutters(
-    const gfx::Size& panel_size,
-    const gfx::RoundedCornersF& panel_radii) {
-  std::vector<std::unique_ptr<RoundedDisplayGutter>> gutters;
-
-  MaybeAppendGutter(gutters, CreateGutter(panel_size, panel_radii,
-                                          0 | RoundedCornerPosition::kUpperLeft,
-                                          /*is_overlay_gutter=*/false));
-  MaybeAppendGutter(gutters,
-                    CreateGutter(panel_size, panel_radii,
-                                 0 | RoundedCornerPosition::kUpperRight,
-                                 /*is_overlay_gutter=*/false));
-  MaybeAppendGutter(gutters, CreateGutter(panel_size, panel_radii,
-                                          0 | RoundedCornerPosition::kLowerLeft,
-                                          /*is_overlay_gutter=*/false));
-  MaybeAppendGutter(gutters,
-                    CreateGutter(panel_size, panel_radii,
-                                 0 | RoundedCornerPosition::kLowerRight,
-                                 /*is_overlay_gutter=*/false));
-
-  return gutters;
-}
-
 }  // namespace ash
diff --git a/ash/rounded_display/rounded_display_gutter_factory.h b/ash/rounded_display/rounded_display_gutter_factory.h
index dd40e339..c59edea5 100644
--- a/ash/rounded_display/rounded_display_gutter_factory.h
+++ b/ash/rounded_display/rounded_display_gutter_factory.h
@@ -35,12 +35,6 @@
       const gfx::Size& panel_size,
       const gfx::RoundedCornersF& panel_radii,
       bool create_vertical_gutters);
-
-  // Creates drawable non-overlay gutters. A non-overlay gutter is considered
-  // drawable if it has at least one RoundedDisplayCorners with non-zero radius.
-  std::vector<std::unique_ptr<RoundedDisplayGutter>> CreateNonOverlayGutters(
-      const gfx::Size& panel_size,
-      const gfx::RoundedCornersF& panel_radii);
 };
 
 }  // namespace ash
diff --git a/ash/rounded_display/rounded_display_gutter_factory_unittest.cc b/ash/rounded_display/rounded_display_gutter_factory_unittest.cc
index 793698ef..83bc4d1 100644
--- a/ash/rounded_display/rounded_display_gutter_factory_unittest.cc
+++ b/ash/rounded_display/rounded_display_gutter_factory_unittest.cc
@@ -90,31 +90,6 @@
 }
 
 TEST_F(RoundedDisplayGutterFactoryTest,
-       NonOverlayGuttersAreNotCreatedIfNotNeeded) {
-  constexpr gfx::RoundedCornersF radii(10, 12, 0, 0);
-
-  auto non_overlay_gutters =
-      factory_->CreateNonOverlayGutters(kTestDisplaySize, radii);
-
-  EXPECT_EQ(non_overlay_gutters.size(), 2u);
-
-  EXPECT_THAT(non_overlay_gutters, testing::Contains(GutterWithMatchingCorners(
-                                       RoundedCornerPosition::kUpperLeft)));
-
-  EXPECT_THAT(non_overlay_gutters, testing::Contains(GutterWithMatchingCorners(
-                                       RoundedCornerPosition::kUpperRight)));
-
-  //  We do not create these non-overlay gutters as lower_right and
-  //  lower_left corners have zero radius.
-  EXPECT_THAT(non_overlay_gutters,
-              testing::Not(testing::Contains(GutterWithMatchingCorners(
-                  RoundedCornerPosition::kLowerLeft))));
-  EXPECT_THAT(non_overlay_gutters,
-              testing::Not(testing::Contains(GutterWithMatchingCorners(
-                  RoundedCornerPosition::kLowerRight))));
-}
-
-TEST_F(RoundedDisplayGutterFactoryTest,
        OverlayGuttersAreNotCreatedIfNotNeeded) {
   {
     const gfx::RoundedCornersF radii(10, 10, 0, 0);
diff --git a/ash/rounded_display/rounded_display_provider.cc b/ash/rounded_display/rounded_display_provider.cc
index 3767ce6..4535b56 100644
--- a/ash/rounded_display/rounded_display_provider.cc
+++ b/ash/rounded_display/rounded_display_provider.cc
@@ -184,10 +184,6 @@
   for (const auto& gutter : overlay_gutters_) {
     gutters.push_back(gutter.get());
   }
-
-  for (const auto& gutter : non_overlay_gutters_) {
-    gutters.push_back(gutter.get());
-  }
 }
 
 bool RoundedDisplayProvider::CreateGutters(
@@ -203,9 +199,6 @@
   overlay_gutters_ = gutter_factory_->CreateOverlayGutters(
       panel_size, panel_radii, create_vertical_gutters);
 
-  non_overlay_gutters_ =
-      gutter_factory_->CreateNonOverlayGutters(panel_size, panel_radii);
-
   return true;
 }
 
diff --git a/ash/rounded_display/rounded_display_provider.h b/ash/rounded_display/rounded_display_provider.h
index 15658599..30ec1740 100644
--- a/ash/rounded_display/rounded_display_provider.h
+++ b/ash/rounded_display/rounded_display_provider.h
@@ -76,10 +76,11 @@
 
   // Creates RoundedDisplayGutters if needed and returns true if we created
   // gutters else returns false.
-  // As an optimization, we create gutters only for non-zero corners of the
-  // display. We can have displays that don't have rounded bottom edges, so we
-  // skip creation of lower-left and lower-right NonOverlayGutters and for the
-  // horizontal orientation, we skip the creation of the lower OverlayGutter.
+  // To minimize the use of overlay planes, we only create a gutter if it has
+  // at least a single non-zero corner mask drawn into it.
+  // For example, for a display that doesn't have rounded bottom edges, and
+  // based on the `strategy_`, we need to create upper and lower OverlayGutters,
+  // we will skip the creation of the lower OverlayGutter.
   bool CreateGutters(const display::Display& display,
                      const gfx::RoundedCornersF& panel_radii);
 
@@ -100,12 +101,9 @@
   // The specified strategy to determine direction of overlay gutters.
   Strategy strategy_ = Strategy::kScanout;
 
-  // Stores the overlay gutters based on the `overlay_gutters_orientation_`.
+  // Stores the overlay gutters that are created based on the `strategy_`.
   std::vector<std::unique_ptr<RoundedDisplayGutter>> overlay_gutters_;
 
-  // Stores the non-overlay gutters.
-  std::vector<std::unique_ptr<RoundedDisplayGutter>> non_overlay_gutters_;
-
   // OverlayRoundedDisplayGutter creation is delegated to this factory.
   std::unique_ptr<RoundedDisplayGutterFactory> gutter_factory_;
 
diff --git a/ash/rounded_display/rounded_display_provider_unittest.cc b/ash/rounded_display/rounded_display_provider_unittest.cc
index efab93a..ebafbe3 100644
--- a/ash/rounded_display/rounded_display_provider_unittest.cc
+++ b/ash/rounded_display/rounded_display_provider_unittest.cc
@@ -94,9 +94,9 @@
   RoundedDisplayProviderTestApi test_api(provider_.get());
   const gfx::RoundedCornersF radii = CreateHorizontallyUniformRadii(10, 12);
 
-  // We expect 4 non-overlays and 2 overlay gutters to be created.
+  // We expect 2 overlay gutters to be created.
   provider_->Init(radii, kDefaultTestStrategy);
-  EXPECT_EQ(test_api.GetGutters().size(), 6u);
+  EXPECT_EQ(test_api.GetGutters().size(), 2u);
 }
 
 TEST_F(RoundedDisplayProviderTest,
@@ -108,7 +108,7 @@
 
   const auto& gutters = test_api.GetGutters();
 
-  EXPECT_EQ(gutters.size(), 6u);
+  EXPECT_EQ(gutters.size(), 2u);
 
   // Check that we have two overlay gutters that in the scanout direction.
   EXPECT_THAT(gutters, testing::Contains(GutterWithMatchingCorners(
@@ -128,7 +128,7 @@
 
   const auto& gutters = test_api.GetGutters();
 
-  EXPECT_EQ(gutters.size(), 6u);
+  EXPECT_EQ(gutters.size(), 2u);
 
   // Check that we have two overlay gutters that in the scanout direction.
   EXPECT_THAT(gutters, testing::Contains(GutterWithMatchingCorners(
diff --git a/ash/webui/common/chrome_os_webui_config.h b/ash/webui/common/chrome_os_webui_config.h
index f84b8e97..4eb7ffc5 100644
--- a/ash/webui/common/chrome_os_webui_config.h
+++ b/ash/webui/common/chrome_os_webui_config.h
@@ -32,7 +32,20 @@
             host,
             [](content::WebUI* web_ui,
                const GURL& url) -> std::unique_ptr<content::WebUIController> {
-              return std::make_unique<T>(web_ui);
+              // We need to determine the correct WebUIController constructor to
+              // use at compile time, depending on whether it requires only
+              // WebUI* or both WebUI* and GURL. We currently don't support
+              // WebUIControllers that have two constructors where one has a
+              // single WebUI* arg and the other has both WebUI* and GURL
+              // params.
+              static_assert(
+                  !(std::is_constructible_v<T, content::WebUI*> &&
+                    std::is_constructible_v<T, content::WebUI*, GURL>));
+              if constexpr (std::is_constructible_v<T, content::WebUI*, GURL>) {
+                return std::make_unique<T>(web_ui, url);
+              } else {
+                return std::make_unique<T>(web_ui);
+              }
             }) {}
 
   // Same as above, but takes in an extra `create_controller_func` argument that
diff --git a/ash/webui/guest_os_installer/BUILD.gn b/ash/webui/guest_os_installer/BUILD.gn
index a9ac8a6..19dac1b8 100644
--- a/ash/webui/guest_os_installer/BUILD.gn
+++ b/ash/webui/guest_os_installer/BUILD.gn
@@ -15,6 +15,7 @@
   ]
 
   deps = [
+    "//ash/webui/common:chrome_os_webui_config",
     "//ash/webui/guest_os_installer/mojom",
     "//ash/webui/guest_os_installer/resources:resources",
     "//content/public/browser",
diff --git a/ash/webui/guest_os_installer/guest_os_installer_ui.cc b/ash/webui/guest_os_installer/guest_os_installer_ui.cc
index 07d9ac7..c7cefca 100644
--- a/ash/webui/guest_os_installer/guest_os_installer_ui.cc
+++ b/ash/webui/guest_os_installer/guest_os_installer_ui.cc
@@ -12,11 +12,8 @@
 namespace ash {
 
 GuestOSInstallerUI::GuestOSInstallerUI(content::WebUI* web_ui,
-                                       const GURL& url,
                                        DelegateFactory delegate_factory)
-    : ui::MojoWebDialogUI(web_ui),
-      url_(url),
-      delegate_factory_(delegate_factory) {
+    : ui::MojoWebDialogUI(web_ui), delegate_factory_(delegate_factory) {
   auto* source = content::WebUIDataSource::CreateAndAdd(
       web_ui->GetWebContents()->GetBrowserContext(),
       ash::kChromeUIGuestOSInstallerHost);
@@ -45,7 +42,7 @@
   // this we delegate actually picking a backend to this delegate factory
   // callback, which lives in //chrome and is passed to us by our constructor
   // (which also lives in //chrome).
-  handler_ = delegate_factory_.Run(this, url_, std::move(pending_page),
+  handler_ = delegate_factory_.Run(this, std::move(pending_page),
                                    std::move(pending_page_handler));
 }
 
diff --git a/ash/webui/guest_os_installer/guest_os_installer_ui.h b/ash/webui/guest_os_installer/guest_os_installer_ui.h
index bd1cc9a..a5e96f0 100644
--- a/ash/webui/guest_os_installer/guest_os_installer_ui.h
+++ b/ash/webui/guest_os_installer/guest_os_installer_ui.h
@@ -7,8 +7,11 @@
 
 #include <memory>
 
+#include "ash/webui/common/chrome_os_webui_config.h"
 #include "ash/webui/guest_os_installer/mojom/guest_os_installer.mojom.h"
+#include "ash/webui/guest_os_installer/url_constants.h"
 #include "base/functional/bind.h"
+#include "content/public/common/url_constants.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -16,6 +19,19 @@
 
 namespace ash {
 
+class GuestOSInstallerUI;
+
+// The WebUIConfig for chrome://guest-os-installer
+class GuestOSInstallerUIConfig
+    : public ChromeOSWebUIConfig<GuestOSInstallerUI> {
+ public:
+  explicit GuestOSInstallerUIConfig(
+      CreateWebUIControllerFunc create_controller_func)
+      : ChromeOSWebUIConfig(content::kChromeUIScheme,
+                            ash::kChromeUIGuestOSInstallerHost,
+                            create_controller_func) {}
+};
+
 // The WebUI for chrome://guest-os-installer
 class GuestOSInstallerUI
     : public ui::MojoWebDialogUI,
@@ -24,13 +40,10 @@
   using DelegateFactory = base::RepeatingCallback<
       std::unique_ptr<ash::guest_os_installer::mojom::PageHandler>(
           GuestOSInstallerUI*,
-          const GURL&,
           mojo::PendingRemote<ash::guest_os_installer::mojom::Page>,
           mojo::PendingReceiver<ash::guest_os_installer::mojom::PageHandler>)>;
 
-  GuestOSInstallerUI(content::WebUI* web_ui,
-                     const GURL& url,
-                     DelegateFactory delegate_factory);
+  GuestOSInstallerUI(content::WebUI* web_ui, DelegateFactory delegate_factory);
 
   GuestOSInstallerUI(const GuestOSInstallerUI&) = delete;
   GuestOSInstallerUI& operator=(const GuestOSInstallerUI&) = delete;
@@ -53,8 +66,6 @@
 
   std::unique_ptr<ash::guest_os_installer::mojom::PageHandler> handler_;
 
-  const GURL url_;
-
   const DelegateFactory delegate_factory_;
 
   WEB_UI_CONTROLLER_TYPE_DECL();
diff --git a/base/allocator/dispatcher/tls.cc b/base/allocator/dispatcher/tls.cc
index c33ac1f..ae9fd7d7 100644
--- a/base/allocator/dispatcher/tls.cc
+++ b/base/allocator/dispatcher/tls.cc
@@ -12,11 +12,26 @@
 
 #include <sys/mman.h>
 
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#include <sys/prctl.h>
+#endif
+
 namespace base::allocator::dispatcher::internal {
 
 void* MMapAllocator::AllocateMemory(size_t size_in_bytes) {
   void* const mmap_res = mmap(nullptr, size_in_bytes, PROT_READ | PROT_WRITE,
                               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX)
+#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
+  if (mmap_res != MAP_FAILED) {
+    // Allow the anonymous memory region allocated by mmap(MAP_ANONYMOUS) to
+    // be identified in /proc/$PID/smaps.  This helps improve visibility into
+    // Chromium's memory usage on Android.
+    prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, mmap_res, size_in_bytes,
+          "tls-mmap-allocator");
+  }
+#endif
+#endif
 
   return (mmap_res != MAP_FAILED) ? mmap_res : nullptr;
 }
@@ -81,4 +96,4 @@
 
 }  // namespace base::allocator::dispatcher::internal
 
-#endif  // USE_LOCAL_TLS_EMULATION()
\ No newline at end of file
+#endif  // USE_LOCAL_TLS_EMULATION()
diff --git a/base/third_party/symbolize/README.chromium b/base/third_party/symbolize/README.chromium
index c7700fec..d797349 100644
--- a/base/third_party/symbolize/README.chromium
+++ b/base/third_party/symbolize/README.chromium
@@ -32,4 +32,6 @@
   platforms.
 - 008-include-cstdlib.patch: include <cstdlib> for abort() rather than relying
   on transitive includes.
-- 009-clang-format.patch: format the source files using Chrome formatting rules.
+- 009-clone-absl-demangle.patch: Clone the demangling implementation from
+  abseil-cpp, which is itself a fork of https://github.com/google/glog/.
+- 010-clang-format.patch: format the source files using Chrome formatting rules.
diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
index da4cbf6a..8db75f0 100644
--- a/base/third_party/symbolize/demangle.cc
+++ b/base/third_party/symbolize/demangle.cc
@@ -34,13 +34,14 @@
 //
 // Note that we only have partial C++0x support yet.
 
-#include <cstdio>  // for NULL
-
 #include "demangle.h"
-#include "utilities.h"
 
 #if defined(GLOG_OS_WINDOWS)
 #include <dbghelp.h>
+#else
+#include <cstdint>
+#include <cstdio>
+#include <limits>
 #endif
 
 _START_GOOGLE_NAMESPACE_
@@ -49,117 +50,199 @@
 typedef struct {
   const char *abbrev;
   const char *real_name;
+  // Number of arguments in <expression> context, or 0 if disallowed.
+  int arity;
 } AbbrevPair;
 
 // List of operators from Itanium C++ ABI.
 static const AbbrevPair kOperatorList[] = {
-  { "nw", "new" },
-  { "na", "new[]" },
-  { "dl", "delete" },
-  { "da", "delete[]" },
-  { "ps", "+" },
-  { "ng", "-" },
-  { "ad", "&" },
-  { "de", "*" },
-  { "co", "~" },
-  { "pl", "+" },
-  { "mi", "-" },
-  { "ml", "*" },
-  { "dv", "/" },
-  { "rm", "%" },
-  { "an", "&" },
-  { "or", "|" },
-  { "eo", "^" },
-  { "aS", "=" },
-  { "pL", "+=" },
-  { "mI", "-=" },
-  { "mL", "*=" },
-  { "dV", "/=" },
-  { "rM", "%=" },
-  { "aN", "&=" },
-  { "oR", "|=" },
-  { "eO", "^=" },
-  { "ls", "<<" },
-  { "rs", ">>" },
-  { "lS", "<<=" },
-  { "rS", ">>=" },
-  { "eq", "==" },
-  { "ne", "!=" },
-  { "lt", "<" },
-  { "gt", ">" },
-  { "le", "<=" },
-  { "ge", ">=" },
-  { "nt", "!" },
-  { "aa", "&&" },
-  { "oo", "||" },
-  { "pp", "++" },
-  { "mm", "--" },
-  { "cm", "," },
-  { "pm", "->*" },
-  { "pt", "->" },
-  { "cl", "()" },
-  { "ix", "[]" },
-  { "qu", "?" },
-  { "st", "sizeof" },
-  { "sz", "sizeof" },
-  { NULL, NULL },
+    // New has special syntax (not currently supported).
+    {"nw", "new", 0},
+    {"na", "new[]", 0},
+
+    // Works except that the 'gs' prefix is not supported.
+    {"dl", "delete", 1},
+    {"da", "delete[]", 1},
+
+    {"ps", "+", 1},  // "positive"
+    {"ng", "-", 1},  // "negative"
+    {"ad", "&", 1},  // "address-of"
+    {"de", "*", 1},  // "dereference"
+    {"co", "~", 1},
+
+    {"pl", "+", 2},
+    {"mi", "-", 2},
+    {"ml", "*", 2},
+    {"dv", "/", 2},
+    {"rm", "%", 2},
+    {"an", "&", 2},
+    {"or", "|", 2},
+    {"eo", "^", 2},
+    {"aS", "=", 2},
+    {"pL", "+=", 2},
+    {"mI", "-=", 2},
+    {"mL", "*=", 2},
+    {"dV", "/=", 2},
+    {"rM", "%=", 2},
+    {"aN", "&=", 2},
+    {"oR", "|=", 2},
+    {"eO", "^=", 2},
+    {"ls", "<<", 2},
+    {"rs", ">>", 2},
+    {"lS", "<<=", 2},
+    {"rS", ">>=", 2},
+    {"eq", "==", 2},
+    {"ne", "!=", 2},
+    {"lt", "<", 2},
+    {"gt", ">", 2},
+    {"le", "<=", 2},
+    {"ge", ">=", 2},
+    {"nt", "!", 1},
+    {"aa", "&&", 2},
+    {"oo", "||", 2},
+    {"pp", "++", 1},
+    {"mm", "--", 1},
+    {"cm", ",", 2},
+    {"pm", "->*", 2},
+    {"pt", "->", 0},  // Special syntax
+    {"cl", "()", 0},  // Special syntax
+    {"ix", "[]", 2},
+    {"qu", "?", 3},
+    {"st", "sizeof", 0},  // Special syntax
+    {"sz", "sizeof", 1},  // Not a real operator name, but used in expressions.
+    {nullptr, nullptr, 0},
 };
 
 // List of builtin types from Itanium C++ ABI.
+//
+// Invariant: only one- or two-character type abbreviations here.
 static const AbbrevPair kBuiltinTypeList[] = {
-  { "v", "void" },
-  { "w", "wchar_t" },
-  { "b", "bool" },
-  { "c", "char" },
-  { "a", "signed char" },
-  { "h", "unsigned char" },
-  { "s", "short" },
-  { "t", "unsigned short" },
-  { "i", "int" },
-  { "j", "unsigned int" },
-  { "l", "long" },
-  { "m", "unsigned long" },
-  { "x", "long long" },
-  { "y", "unsigned long long" },
-  { "n", "__int128" },
-  { "o", "unsigned __int128" },
-  { "f", "float" },
-  { "d", "double" },
-  { "e", "long double" },
-  { "g", "__float128" },
-  { "z", "ellipsis" },
-  { NULL, NULL }
+    {"v", "void", 0},
+    {"w", "wchar_t", 0},
+    {"b", "bool", 0},
+    {"c", "char", 0},
+    {"a", "signed char", 0},
+    {"h", "unsigned char", 0},
+    {"s", "short", 0},
+    {"t", "unsigned short", 0},
+    {"i", "int", 0},
+    {"j", "unsigned int", 0},
+    {"l", "long", 0},
+    {"m", "unsigned long", 0},
+    {"x", "long long", 0},
+    {"y", "unsigned long long", 0},
+    {"n", "__int128", 0},
+    {"o", "unsigned __int128", 0},
+    {"f", "float", 0},
+    {"d", "double", 0},
+    {"e", "long double", 0},
+    {"g", "__float128", 0},
+    {"z", "ellipsis", 0},
+
+    {"De", "decimal128", 0},  // IEEE 754r decimal floating point (128 bits)
+    {"Dd", "decimal64", 0},   // IEEE 754r decimal floating point (64 bits)
+    {"Dc", "decltype(auto)", 0},
+    {"Da", "auto", 0},
+    {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
+    {"Df", "decimal32", 0},       // IEEE 754r decimal floating point (32 bits)
+    {"Di", "char32_t", 0},
+    {"Du", "char8_t", 0},
+    {"Ds", "char16_t", 0},
+    {"Dh", "float16", 0},  // IEEE 754r half-precision float (16 bits)
+    {nullptr, nullptr, 0},
 };
 
 // List of substitutions Itanium C++ ABI.
 static const AbbrevPair kSubstitutionList[] = {
-  { "St", "" },
-  { "Sa", "allocator" },
-  { "Sb", "basic_string" },
-  // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
-  { "Ss", "string"},
-  // std::basic_istream<char, std::char_traits<char> >
-  { "Si", "istream" },
-  // std::basic_ostream<char, std::char_traits<char> >
-  { "So", "ostream" },
-  // std::basic_iostream<char, std::char_traits<char> >
-  { "Sd", "iostream" },
-  { NULL, NULL }
+    {"St", "", 0},
+    {"Sa", "allocator", 0},
+    {"Sb", "basic_string", 0},
+    // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
+    {"Ss", "string", 0},
+    // std::basic_istream<char, std::char_traits<char> >
+    {"Si", "istream", 0},
+    // std::basic_ostream<char, std::char_traits<char> >
+    {"So", "ostream", 0},
+    // std::basic_iostream<char, std::char_traits<char> >
+    {"Sd", "iostream", 0},
+    {nullptr, nullptr, 0},
 };
 
-// State needed for demangling.
+// State needed for demangling.  This struct is copied in almost every stack
+// frame, so every byte counts.
 typedef struct {
-  const char* mangled_cur;   // Cursor of mangled name.
-  char* out_cur;             // Cursor of output string.
-  const char* out_begin;     // Beginning of output string.
-  const char* out_end;       // End of output string.
-  const char* prev_name;     // For constructors/destructors.
-  ssize_t prev_name_length;  // For constructors/destructors.
-  short nest_level;          // For nested names.
-  bool append;               // Append flag.
-  bool overflowed;           // True if output gets overflowed.
+  int mangled_idx;                     // Cursor of mangled name.
+  int out_cur_idx;                     // Cursor of output string.
+  int prev_name_idx;                   // For constructors/destructors.
+  unsigned int prev_name_length : 16;  // For constructors/destructors.
+  signed int nest_level : 15;          // For nested names.
+  unsigned int append : 1;             // Append flag.
+  // Note: for some reason MSVC can't pack "bool append : 1" into the same int
+  // with the above two fields, so we use an int instead.  Amusingly it can pack
+  // "signed bool" as expected, but relying on that to continue to be a legal
+  // type seems ill-advised (as it's illegal in at least clang).
+} ParseState;
+
+static_assert(sizeof(ParseState) == 4 * sizeof(int),
+              "unexpected size of ParseState");
+
+// One-off state for demangling that's not subject to backtracking -- either
+// constant data, data that's intentionally immune to backtracking (steps), or
+// data that would never be changed by backtracking anyway (recursion_depth).
+//
+// Only one copy of this exists for each call to Demangle, so the size of this
+// struct is nearly inconsequential.
+typedef struct {
+  const char* mangled_begin;  // Beginning of input string.
+  char* out;                  // Beginning of output string.
+  int out_end_idx;            // One past last allowed output character.
+  int recursion_depth;        // For stack exhaustion prevention.
+  int steps;               // Cap how much work we'll do, regardless of depth.
+  ParseState parse_state;  // Backtrackable state copied for most frames.
 } State;
 
+namespace {
+// Prevent deep recursion / stack exhaustion.
+// Also prevent unbounded handling of complex inputs.
+class ComplexityGuard {
+ public:
+  explicit ComplexityGuard(State* state) : state_(state) {
+    ++state->recursion_depth;
+    ++state->steps;
+  }
+  ~ComplexityGuard() { --state_->recursion_depth; }
+
+  // 256 levels of recursion seems like a reasonable upper limit on depth.
+  // 128 is not enough to demagle synthetic tests from demangle_unittest.txt:
+  // "_ZaaZZZZ..." and "_ZaaZcvZcvZ..."
+  static constexpr int kRecursionDepthLimit = 256;
+
+  // We're trying to pick a charitable upper-limit on how many parse steps are
+  // necessary to handle something that a human could actually make use of.
+  // This is mostly in place as a bound on how much work we'll do if we are
+  // asked to demangle an mangled name from an untrusted source, so it should be
+  // much larger than the largest expected symbol, but much smaller than the
+  // amount of work we can do in, e.g., a second.
+  //
+  // Some real-world symbols from an arbitrary binary started failing between
+  // 2^12 and 2^13, so we multiply the latter by an extra factor of 16 to set
+  // the limit.
+  //
+  // Spending one second on 2^17 parse steps would require each step to take
+  // 7.6us, or ~30000 clock cycles, so it's safe to say this can be done in
+  // under a second.
+  static constexpr int kParseStepsLimit = 1 << 17;
+
+  bool IsTooComplex() const {
+    return state_->recursion_depth > kRecursionDepthLimit ||
+           state_->steps > kParseStepsLimit;
+  }
+
+ private:
+  State* state_;
+};
+}  // namespace
+
 // We don't use strlen() in libc since it's not guaranteed to be async
 // signal safe.
 static size_t StrLen(const char *str) {
@@ -172,8 +255,8 @@
 }
 
 // Returns true if "str" has at least "n" characters remaining.
-static bool AtLeastNumCharsRemaining(const char* str, ssize_t n) {
-  for (ssize_t i = 0; i < n; ++i) {
+static bool AtLeastNumCharsRemaining(const char* str, size_t n) {
+  for (size_t i = 0; i < n; ++i) {
     if (str[i] == '\0') {
       return false;
     }
@@ -184,8 +267,7 @@
 // Returns true if "str" has "prefix" as a prefix.
 static bool StrPrefix(const char *str, const char *prefix) {
   size_t i = 0;
-  while (str[i] != '\0' && prefix[i] != '\0' &&
-         str[i] == prefix[i]) {
+  while (str[i] != '\0' && prefix[i] != '\0' && str[i] == prefix[i]) {
     ++i;
   }
   return prefix[i] == '\0';  // Consumed everything in "prefix".
@@ -195,23 +277,34 @@
                       const char* mangled,
                       char* out,
                       size_t out_size) {
-  state->mangled_cur = mangled;
-  state->out_cur = out;
-  state->out_begin = out;
-  state->out_end = out + out_size;
-  state->prev_name  = NULL;
-  state->prev_name_length = -1;
-  state->nest_level = -1;
-  state->append = true;
-  state->overflowed = false;
+  state->mangled_begin = mangled;
+  state->out = out;
+  state->out_end_idx = static_cast<int>(out_size);
+  state->recursion_depth = 0;
+  state->steps = 0;
+
+  state->parse_state.mangled_idx = 0;
+  state->parse_state.out_cur_idx = 0;
+  state->parse_state.prev_name_idx = 0;
+  state->parse_state.prev_name_length = 0;
+  state->parse_state.nest_level = -1;
+  state->parse_state.append = true;
 }
 
-// Returns true and advances "mangled_cur" if we find "one_char_token"
-// at "mangled_cur" position.  It is assumed that "one_char_token" does
+static inline const char* RemainingInput(State* state) {
+  return &state->mangled_begin[state->parse_state.mangled_idx];
+}
+
+// Returns true and advances "mangled_idx" if we find "one_char_token"
+// at "mangled_idx" position.  It is assumed that "one_char_token" does
 // not contain '\0'.
 static bool ParseOneCharToken(State *state, const char one_char_token) {
-  if (state->mangled_cur[0] == one_char_token) {
-    ++state->mangled_cur;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == one_char_token) {
+    ++state->parse_state.mangled_idx;
     return true;
   }
   return false;
@@ -221,9 +314,13 @@
 // at "mangled_cur" position.  It is assumed that "two_char_token" does
 // not contain '\0'.
 static bool ParseTwoCharToken(State *state, const char *two_char_token) {
-  if (state->mangled_cur[0] == two_char_token[0] &&
-      state->mangled_cur[1] == two_char_token[1]) {
-    state->mangled_cur += 2;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == two_char_token[0] &&
+      RemainingInput(state)[1] == two_char_token[1]) {
+    state->parse_state.mangled_idx += 2;
     return true;
   }
   return false;
@@ -232,18 +329,36 @@
 // Returns true and advances "mangled_cur" if we find any character in
 // "char_class" at "mangled_cur" position.
 static bool ParseCharClass(State *state, const char *char_class) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (RemainingInput(state)[0] == '\0') {
+    return false;
+  }
   const char *p = char_class;
   for (; *p != '\0'; ++p) {
-    if (state->mangled_cur[0] == *p) {
-      ++state->mangled_cur;
+    if (RemainingInput(state)[0] == *p) {
+      ++state->parse_state.mangled_idx;
       return true;
     }
   }
   return false;
 }
 
+static bool ParseDigit(State* state, int* digit) {
+  char c = RemainingInput(state)[0];
+  if (ParseCharClass(state, "0123456789")) {
+    if (digit != nullptr) {
+      *digit = c - '0';
+    }
+    return true;
+  }
+  return false;
+}
+
 // This function is used for handling an optional non-terminal.
-static bool Optional(bool) {
+static bool Optional(bool /*status*/) {
   return true;
 }
 
@@ -268,21 +383,23 @@
   return true;
 }
 
-// Append "str" at "out_cur".  If there is an overflow, "overflowed"
-// is set to true for later use.  The output string is ensured to
+// Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
+// set to out_end_idx+1.  The output string is ensured to
 // always terminate with '\0' as long as there is no overflow.
-static void Append(State* state, const char* const str, ssize_t length) {
-  for (ssize_t i = 0; i < length; ++i) {
-    if (state->out_cur + 1 < state->out_end) {  // +1 for '\0'
-      *state->out_cur = str[i];
-      ++state->out_cur;
+static void Append(State* state, const char* const str, const size_t length) {
+  for (size_t i = 0; i < length; ++i) {
+    if (state->parse_state.out_cur_idx + 1 <
+        state->out_end_idx) {  // +1 for '\0'
+      state->out[state->parse_state.out_cur_idx++] = str[i];
     } else {
-      state->overflowed = true;
+      // signal overflow
+      state->parse_state.out_cur_idx = state->out_end_idx + 1;
       break;
     }
   }
-  if (!state->overflowed) {
-    *state->out_cur = '\0';  // Terminate it with '\0'
+  if (state->parse_state.out_cur_idx < state->out_end_idx) {
+    state->out[state->parse_state.out_cur_idx] =
+        '\0';  // Terminate it with '\0'
   }
 }
 
@@ -300,140 +417,176 @@
 }
 
 // Returns true if "str" is a function clone suffix.  These suffixes are used
-// by GCC 4.5.x and later versions to indicate functions which have been
-// cloned during optimization.  We treat any sequence (.<alpha>+.<digit>+)+ as
-// a function clone suffix.
+// by GCC 4.5.x and later versions (and our locally-modified version of GCC
+// 4.4.x) to indicate functions which have been cloned during optimization.
+// We treat any sequence (.<alpha>+.<digit>+)+ as a function clone suffix.
+// Additionally, '_' is allowed along with the alphanumeric sequence.
 static bool IsFunctionCloneSuffix(const char *str) {
   size_t i = 0;
   while (str[i] != '\0') {
-    // Consume a single .<alpha>+.<digit>+ sequence.
-    if (str[i] != '.' || !IsAlpha(str[i + 1])) {
+    bool parsed = false;
+    // Consume a single [.<alpha> | _]*[.<digit>]* sequence.
+    if (str[i] == '.' && (IsAlpha(str[i + 1]) || str[i + 1] == '_')) {
+      parsed = true;
+      i += 2;
+      while (IsAlpha(str[i]) || str[i] == '_') {
+        ++i;
+      }
+    }
+    if (str[i] == '.' && IsDigit(str[i + 1])) {
+      parsed = true;
+      i += 2;
+      while (IsDigit(str[i])) {
+        ++i;
+      }
+    }
+    if (!parsed) {
       return false;
     }
-    i += 2;
-    while (IsAlpha(str[i])) {
-      ++i;
-    }
-    if (str[i] != '.' || !IsDigit(str[i + 1])) {
-      return false;
-    }
-    i += 2;
-    while (IsDigit(str[i])) {
-      ++i;
-    }
   }
   return true;  // Consumed everything in "str".
 }
 
+static bool EndsWith(State* state, const char chr) {
+  return state->parse_state.out_cur_idx > 0 &&
+         state->parse_state.out_cur_idx < state->out_end_idx &&
+         chr == state->out[state->parse_state.out_cur_idx - 1];
+}
+
 // Append "str" with some tweaks, iff "append" state is true.
-// Returns true so that it can be placed in "if" conditions.
 static void MaybeAppendWithLength(State* state,
                                   const char* const str,
-                                  ssize_t length) {
-  if (state->append && length > 0) {
+                                  const size_t length) {
+  if (state->parse_state.append && length > 0) {
     // Append a space if the output buffer ends with '<' and "str"
     // starts with '<' to avoid <<<.
-    if (str[0] == '<' && state->out_begin < state->out_cur  &&
-        state->out_cur[-1] == '<') {
+    if (str[0] == '<' && EndsWith(state, '<')) {
       Append(state, " ", 1);
     }
-    // Remember the last identifier name for ctors/dtors.
-    if (IsAlpha(str[0]) || str[0] == '_') {
-      state->prev_name = state->out_cur;
-      state->prev_name_length = length;
+    // Remember the last identifier name for ctors/dtors,
+    // but only if we haven't yet overflown the buffer.
+    if (state->parse_state.out_cur_idx < state->out_end_idx &&
+        (IsAlpha(str[0]) || str[0] == '_')) {
+      state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
+      state->parse_state.prev_name_length = static_cast<unsigned int>(length);
     }
     Append(state, str, length);
   }
 }
 
-// A convenient wrapper arount MaybeAppendWithLength().
-static bool MaybeAppend(State *state, const char * const str) {
-  if (state->append) {
+// Appends a positive decimal number to the output if appending is enabled.
+static bool MaybeAppendDecimal(State* state, int val) {
+  // Max {32-64}-bit unsigned int is 20 digits.
+  constexpr size_t kMaxLength = 20;
+  char buf[kMaxLength];
+
+  // We can't use itoa or sprintf as neither is specified to be
+  // async-signal-safe.
+  if (state->parse_state.append) {
+    // We can't have a one-before-the-beginning pointer, so instead start with
+    // one-past-the-end and manipulate one character before the pointer.
+    char* p = &buf[kMaxLength];
+    do {  // val=0 is the only input that should write a leading zero digit.
+      *--p = static_cast<char>((val % 10) + '0');
+      val /= 10;
+    } while (p > buf && val != 0);
+
+    // 'p' landed on the last character we set.  How convenient.
+    Append(state, p, kMaxLength - static_cast<size_t>(p - buf));
+  }
+
+  return true;
+}
+
+// A convenient wrapper around MaybeAppendWithLength().
+// Returns true so that it can be placed in "if" conditions.
+static bool MaybeAppend(State* state, const char* const str) {
+  if (state->parse_state.append) {
     size_t length = StrLen(str);
-    MaybeAppendWithLength(state, str, static_cast<ssize_t>(length));
+    MaybeAppendWithLength(state, str, length);
   }
   return true;
 }
 
 // This function is used for handling nested names.
 static bool EnterNestedName(State *state) {
-  state->nest_level = 0;
+  state->parse_state.nest_level = 0;
   return true;
 }
 
 // This function is used for handling nested names.
-static bool LeaveNestedName(State *state, short prev_value) {
-  state->nest_level = prev_value;
+static bool LeaveNestedName(State* state, int16_t prev_value) {
+  state->parse_state.nest_level = prev_value;
   return true;
 }
 
 // Disable the append mode not to print function parameters, etc.
 static bool DisableAppend(State *state) {
-  state->append = false;
+  state->parse_state.append = false;
   return true;
 }
 
 // Restore the append mode to the previous state.
 static bool RestoreAppend(State *state, bool prev_value) {
-  state->append = prev_value;
+  state->parse_state.append = prev_value;
   return true;
 }
 
 // Increase the nest level for nested names.
 static void MaybeIncreaseNestLevel(State *state) {
-  if (state->nest_level > -1) {
-    ++state->nest_level;
+  if (state->parse_state.nest_level > -1) {
+    ++state->parse_state.nest_level;
   }
 }
 
 // Appends :: for nested names if necessary.
 static void MaybeAppendSeparator(State *state) {
-  if (state->nest_level >= 1) {
+  if (state->parse_state.nest_level >= 1) {
     MaybeAppend(state, "::");
   }
 }
 
 // Cancel the last separator if necessary.
 static void MaybeCancelLastSeparator(State *state) {
-  if (state->nest_level >= 1 && state->append &&
-      state->out_begin <= state->out_cur - 2) {
-    state->out_cur -= 2;
-    *state->out_cur = '\0';
+  if (state->parse_state.nest_level >= 1 && state->parse_state.append &&
+      state->parse_state.out_cur_idx >= 2) {
+    state->parse_state.out_cur_idx -= 2;
+    state->out[state->parse_state.out_cur_idx] = '\0';
   }
 }
 
 // Returns true if the identifier of the given length pointed to by
 // "mangled_cur" is anonymous namespace.
-static bool IdentifierIsAnonymousNamespace(State* state, ssize_t length) {
+static bool IdentifierIsAnonymousNamespace(State* state, size_t length) {
+  // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
   static const char anon_prefix[] = "_GLOBAL__N_";
-  return (length > static_cast<ssize_t>(sizeof(anon_prefix)) -
-                       1 &&  // Should be longer.
-          StrPrefix(state->mangled_cur, anon_prefix));
+  return (length > (sizeof(anon_prefix) - 1) &&
+          StrPrefix(RemainingInput(state), anon_prefix));
 }
 
 // Forward declarations of our parsing functions.
 static bool ParseMangledName(State *state);
 static bool ParseEncoding(State *state);
 static bool ParseName(State *state);
-static bool ParseUnscopedName(State *state);
-static bool ParseUnscopedTemplateName(State *state);
+static bool ParseUnscopedName(State* state);
 static bool ParseNestedName(State *state);
 static bool ParsePrefix(State *state);
 static bool ParseUnqualifiedName(State *state);
 static bool ParseSourceName(State *state);
 static bool ParseLocalSourceName(State *state);
+static bool ParseUnnamedTypeName(State* state);
 static bool ParseNumber(State *state, int *number_out);
 static bool ParseFloatNumber(State *state);
 static bool ParseSeqId(State *state);
-static bool ParseIdentifier(State* state, ssize_t length);
-static bool ParseAbiTags(State *state);
-static bool ParseAbiTag(State *state);
-static bool ParseOperatorName(State *state);
+static bool ParseIdentifier(State* state, size_t length);
+static bool ParseOperatorName(State* state, int* arity);
 static bool ParseSpecialName(State *state);
 static bool ParseCallOffset(State *state);
 static bool ParseNVOffset(State *state);
 static bool ParseVOffset(State *state);
+static bool ParseAbiTags(State* state);
 static bool ParseCtorDtorName(State *state);
+static bool ParseDecltype(State* state);
 static bool ParseType(State *state);
 static bool ParseCVQualifiers(State *state);
 static bool ParseBuiltinType(State *state);
@@ -446,11 +599,15 @@
 static bool ParseTemplateTemplateParam(State *state);
 static bool ParseTemplateArgs(State *state);
 static bool ParseTemplateArg(State *state);
+static bool ParseBaseUnresolvedName(State* state);
+static bool ParseUnresolvedName(State* state);
 static bool ParseExpression(State *state);
 static bool ParseExprPrimary(State *state);
+static bool ParseExprCastValue(State* state);
 static bool ParseLocalName(State *state);
+static bool ParseLocalNameSuffix(State* state);
 static bool ParseDiscriminator(State *state);
-static bool ParseSubstitution(State *state);
+static bool ParseSubstitution(State* state, bool accept_std);
 
 // Implementation note: the following code is a straightforward
 // translation of the Itanium C++ ABI defined in BNF with a couple of
@@ -462,11 +619,12 @@
 // - Reorder patterns to give greedier functions precedence
 //   We'll mark "Less greedy than" for these cases in the code
 //
-// Each parsing function changes the state and returns true on
-// success.  Otherwise, don't change the state and returns false.  To
-// ensure that the state isn't changed in the latter case, we save the
-// original state before we call more than one parsing functions
-// consecutively with &&, and restore the state if unsuccessful.  See
+// Each parsing function changes the parse state and returns true on
+// success, or returns false and doesn't change the parse state (note:
+// the parse-steps counter increases regardless of success or failure).
+// To ensure that the parse state isn't changed in the latter case, we
+// save the original state before we call multiple parsing functions
+// consecutively with &&, and restore it if unsuccessful.  See
 // ParseEncoding() as an example of this convention.  We follow the
 // convention throughout the code.
 //
@@ -480,10 +638,14 @@
 //
 // Reference:
 // - Itanium C++ ABI
-//   <http://www.codesourcery.com/cxx-abi/abi.html#mangling>
+//   <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>
 
 // <mangled-name> ::= _Z <encoding>
 static bool ParseMangledName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
 }
 
@@ -491,13 +653,20 @@
 //            ::= <(data) name>
 //            ::= <special-name>
 static bool ParseEncoding(State *state) {
-  State copy = *state;
-  if (ParseName(state) && ParseBareFunctionType(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  // Implementing the first two productions together as <name>
+  // [<bare-function-type>] avoids exponential blowup of backtracking.
+  //
+  // Since Optional(...) can't fail, there's no need to copy the state for
+  // backtracking.
+  if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
     return true;
   }
-  *state = copy;
 
-  if (ParseName(state) || ParseSpecialName(state)) {
+  if (ParseSpecialName(state)) {
     return true;
   }
   return false;
@@ -508,60 +677,79 @@
 //        ::= <unscoped-name>
 //        ::= <local-name>
 static bool ParseName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseNestedName(state) || ParseLocalName(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseUnscopedTemplateName(state) &&
+  // We reorganize the productions to avoid re-parsing unscoped names.
+  // - Inline <unscoped-template-name> productions:
+  //   <name> ::= <substitution> <template-args>
+  //          ::= <unscoped-name> <template-args>
+  //          ::= <unscoped-name>
+  // - Merge the two productions that start with unscoped-name:
+  //   <name> ::= <unscoped-name> [<template-args>]
+
+  ParseState copy = state->parse_state;
+  // "std<...>" isn't a valid name.
+  if (ParseSubstitution(state, /*accept_std=*/false) &&
       ParseTemplateArgs(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  // Less greedy than <unscoped-template-name> <template-args>.
-  if (ParseUnscopedName(state)) {
-    return true;
-  }
-  return false;
+  // Note there's no need to restore state after this since only the first
+  // subparser can fail.
+  return ParseUnscopedName(state) && Optional(ParseTemplateArgs(state));
 }
 
 // <unscoped-name> ::= <unqualified-name>
 //                 ::= St <unqualified-name>
 static bool ParseUnscopedName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseUnqualifiedName(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseTwoCharToken(state, "St") &&
-      MaybeAppend(state, "std::") &&
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "St") && MaybeAppend(state, "std::") &&
       ParseUnqualifiedName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <unscoped-template-name> ::= <unscoped-name>
-//                          ::= <substitution>
-static bool ParseUnscopedTemplateName(State *state) {
-  return ParseUnscopedName(state) || ParseSubstitution(state);
+// <ref-qualifer> ::= R // lvalue method reference qualifier
+//                ::= O // rvalue method reference qualifier
+static inline bool ParseRefQualifier(State* state) {
+  return ParseCharClass(state, "OR");
 }
 
-// <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
-//               ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
+// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix>
+//                   <unqualified-name> E
+//               ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix>
+//                   <template-args> E
 static bool ParseNestedName(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'N') &&
-      EnterNestedName(state) &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
       Optional(ParseCVQualifiers(state)) &&
-      ParsePrefix(state) &&
+      Optional(ParseRefQualifier(state)) && ParsePrefix(state) &&
       LeaveNestedName(state, copy.nest_level) &&
       ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
@@ -577,12 +765,17 @@
 //                   ::= <template-param>
 //                   ::= <substitution>
 static bool ParsePrefix(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   bool has_something = false;
   while (true) {
     MaybeAppendSeparator(state);
     if (ParseTemplateParam(state) ||
-        ParseSubstitution(state) ||
-        ParseUnscopedName(state)) {
+        ParseSubstitution(state, /*accept_std=*/true) ||
+        ParseUnscopedName(state) ||
+        (ParseOneCharToken(state, 'M') && ParseUnnamedTypeName(state))) {
       has_something = true;
       MaybeIncreaseNestLevel(state);
       continue;
@@ -597,40 +790,122 @@
   return true;
 }
 
-// <unqualified-name> ::= <operator-name>
-//                    ::= <ctor-dtor-name>
+// <unqualified-name> ::= <operator-name> [<abi-tags>]
+//                    ::= <ctor-dtor-name> [<abi-tags>]
 //                    ::= <source-name> [<abi-tags>]
 //                    ::= <local-source-name> [<abi-tags>]
+//                    ::= <unnamed-type-name> [<abi-tags>]
+//
+// <local-source-name> is a GCC extension; see below.
 static bool ParseUnqualifiedName(State *state) {
-  return (ParseOperatorName(state) ||
-          ParseCtorDtorName(state) ||
-          (ParseSourceName(state) && Optional(ParseAbiTags(state))) ||
-          (ParseLocalSourceName(state) && Optional(ParseAbiTags(state))));
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+      ParseSourceName(state) || ParseLocalSourceName(state) ||
+      ParseUnnamedTypeName(state)) {
+    return ParseAbiTags(state);
+  }
+  return false;
+}
+
+// <abi-tags> ::= <abi-tag> [<abi-tags>]
+// <abi-tag>  ::= B <source-name>
+static bool ParseAbiTags(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  while (ParseOneCharToken(state, 'B')) {
+    ParseState copy = state->parse_state;
+    MaybeAppend(state, "[abi:");
+
+    if (!ParseSourceName(state)) {
+      state->parse_state = copy;
+      return false;
+    }
+    MaybeAppend(state, "]");
+  }
+
+  return true;
 }
 
 // <source-name> ::= <positive length number> <identifier>
 static bool ParseSourceName(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   int length = -1;
-  if (ParseNumber(state, &length) && ParseIdentifier(state, length)) {
+  if (ParseNumber(state, &length) &&
+      ParseIdentifier(state, static_cast<size_t>(length))) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <local-source-name> ::= L <source-name> [<discriminator>]
 //
 // References:
-//   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
-//   http://gcc.gnu.org/viewcvs?view=rev&revision=124467
+//   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
+//   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
 static bool ParseLocalSourceName(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
       Optional(ParseDiscriminator(state))) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+  return false;
+}
+
+// <unnamed-type-name> ::= Ut [<(nonnegative) number>] _
+//                     ::= <closure-type-name>
+// <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
+// <lambda-sig>        ::= <(parameter) type>+
+static bool ParseUnnamedTypeName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
+  // Optionally parse the encoded value into 'which' and add 2 to get the index.
+  int which = -1;
+
+  // Unnamed type local to function or class.
+  if (ParseTwoCharToken(state, "Ut") && Optional(ParseNumber(state, &which)) &&
+      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
+      ParseOneCharToken(state, '_')) {
+    MaybeAppend(state, "{unnamed type#");
+    MaybeAppendDecimal(state, 2 + which);
+    MaybeAppend(state, "}");
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Closure type.
+  which = -1;
+  if (ParseTwoCharToken(state, "Ul") && DisableAppend(state) &&
+      OneOrMore(ParseType, state) && RestoreAppend(state, copy.append) &&
+      ParseOneCharToken(state, 'E') && Optional(ParseNumber(state, &which)) &&
+      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
+      ParseOneCharToken(state, '_')) {
+    MaybeAppend(state, "{lambda()#");
+    MaybeAppendDecimal(state, 2 + which);
+    MaybeAppend(state, "}");
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
@@ -638,23 +913,34 @@
 // If "number_out" is non-null, then *number_out is set to the value of the
 // parsed number on success.
 static bool ParseNumber(State *state, int *number_out) {
-  int sign = 1;
-  if (ParseOneCharToken(state, 'n')) {
-    sign = -1;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  const char *p = state->mangled_cur;
-  int number = 0;
-  for (;*p != '\0'; ++p) {
+  bool negative = false;
+  if (ParseOneCharToken(state, 'n')) {
+    negative = true;
+  }
+  const char* p = RemainingInput(state);
+  uint64_t number = 0;
+  for (; *p != '\0'; ++p) {
     if (IsDigit(*p)) {
-      number = number * 10 + (*p - '0');
+      number = number * 10 + static_cast<uint64_t>(*p - '0');
     } else {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
-    if (number_out != NULL) {
-      *number_out = number * sign;
+  // Apply the sign with uint64_t arithmetic so overflows aren't UB.  Gives
+  // "incorrect" results for out-of-range inputs, but negative values only
+  // appear for literals, which aren't printed.
+  if (negative) {
+    number = ~number + 1;
+  }
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
+    if (number_out != nullptr) {
+      // Note: possibly truncate "number".
+      *number_out = static_cast<int>(number);
     }
     return true;
   }
@@ -664,14 +950,18 @@
 // Floating-point literals are encoded using a fixed-length lowercase
 // hexadecimal string.
 static bool ParseFloatNumber(State *state) {
-  const char *p = state->mangled_cur;
-  for (;*p != '\0'; ++p) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  const char* p = RemainingInput(state);
+  for (; *p != '\0'; ++p) {
     if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
     return true;
   }
   return false;
@@ -680,93 +970,91 @@
 // The <seq-id> is a sequence number in base 36,
 // using digits and upper case letters
 static bool ParseSeqId(State *state) {
-  const char *p = state->mangled_cur;
-  for (;*p != '\0'; ++p) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  const char* p = RemainingInput(state);
+  for (; *p != '\0'; ++p) {
     if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
       break;
     }
   }
-  if (p != state->mangled_cur) {  // Conversion succeeded.
-    state->mangled_cur = p;
+  if (p != RemainingInput(state)) {  // Conversion succeeded.
+    state->parse_state.mangled_idx += p - RemainingInput(state);
     return true;
   }
   return false;
 }
 
 // <identifier> ::= <unqualified source code identifier> (of given length)
-static bool ParseIdentifier(State* state, ssize_t length) {
-  if (length == -1 ||
-      !AtLeastNumCharsRemaining(state->mangled_cur, length)) {
+static bool ParseIdentifier(State* state, size_t length) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
     return false;
   }
   if (IdentifierIsAnonymousNamespace(state, length)) {
     MaybeAppend(state, "(anonymous namespace)");
   } else {
-    MaybeAppendWithLength(state, state->mangled_cur, length);
+    MaybeAppendWithLength(state, RemainingInput(state), length);
   }
-  state->mangled_cur += length;
+  state->parse_state.mangled_idx += length;
   return true;
 }
 
-// <abi-tags> ::= <abi-tag> [<abi-tags>]
-static bool ParseAbiTags(State *state) {
-  State copy = *state;
-  DisableAppend(state);
-  if (OneOrMore(ParseAbiTag, state)) {
-    RestoreAppend(state, copy.append);
-    return true;
-  }
-  *state = copy;
-  return false;
-}
-
-// <abi-tag> ::= B <source-name>
-static bool ParseAbiTag(State *state) {
-  return ParseOneCharToken(state, 'B') && ParseSourceName(state);
-}
-
 // <operator-name> ::= nw, and other two letters cases
 //                 ::= cv <type>  # (cast)
 //                 ::= v  <digit> <source-name> # vendor extended operator
-static bool ParseOperatorName(State *state) {
-  if (!AtLeastNumCharsRemaining(state->mangled_cur, 2)) {
+static bool ParseOperatorName(State* state, int* arity) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
     return false;
   }
   // First check with "cv" (cast) case.
-  State copy = *state;
-  if (ParseTwoCharToken(state, "cv") &&
-      MaybeAppend(state, "operator ") &&
-      EnterNestedName(state) &&
-      ParseType(state) &&
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "cv") && MaybeAppend(state, "operator ") &&
+      EnterNestedName(state) && ParseType(state) &&
       LeaveNestedName(state, copy.nest_level)) {
+    if (arity != nullptr) {
+      *arity = 1;
+    }
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Then vendor extended operators.
-  if (ParseOneCharToken(state, 'v') && ParseCharClass(state, "0123456789") &&
+  if (ParseOneCharToken(state, 'v') && ParseDigit(state, arity) &&
       ParseSourceName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Other operator names should start with a lower alphabet followed
   // by a lower/upper alphabet.
-  if (!(IsLower(state->mangled_cur[0]) &&
-        IsAlpha(state->mangled_cur[1]))) {
+  if (!(IsLower(RemainingInput(state)[0]) &&
+        IsAlpha(RemainingInput(state)[1]))) {
     return false;
   }
   // We may want to perform a binary search if we really need speed.
   const AbbrevPair *p;
-  for (p = kOperatorList; p->abbrev != NULL; ++p) {
-    if (state->mangled_cur[0] == p->abbrev[0] &&
-        state->mangled_cur[1] == p->abbrev[1]) {
+  for (p = kOperatorList; p->abbrev != nullptr; ++p) {
+    if (RemainingInput(state)[0] == p->abbrev[0] &&
+        RemainingInput(state)[1] == p->abbrev[1]) {
+      if (arity != nullptr) {
+        *arity = p->arity;
+      }
       MaybeAppend(state, "operator");
       if (IsLower(*p->real_name)) {  // new, delete, etc.
         MaybeAppend(state, " ");
       }
       MaybeAppend(state, p->real_name);
-      state->mangled_cur += 2;
+      state->parse_state.mangled_idx += 2;
       return true;
     }
   }
@@ -777,6 +1065,7 @@
 //                ::= TT <type>
 //                ::= TI <type>
 //                ::= TS <type>
+//                ::= TH <type>  # thread-local
 //                ::= Tc <call-offset> <call-offset> <(base) encoding>
 //                ::= GV <(object) name>
 //                ::= T <call-offset> <(base) encoding>
@@ -792,123 +1081,168 @@
 // Note: we don't care much about them since they don't appear in
 // stack traces.  The are special data.
 static bool ParseSpecialName(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'T') &&
-      ParseCharClass(state, "VTIS") &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
       ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) &&
       ParseCallOffset(state) && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoCharToken(state, "GV") &&
-      ParseName(state)) {
+  if (ParseTwoCharToken(state, "GV") && ParseName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) &&
       ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // G++ extensions
   if (ParseTwoCharToken(state, "TC") && ParseType(state) &&
-      ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
-      DisableAppend(state) &&
-      ParseType(state)) {
+      ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+      DisableAppend(state) && ParseType(state)) {
     RestoreAppend(state, copy.append);
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") &&
       ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseTwoCharToken(state, "GR") && ParseName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") &&
       ParseCallOffset(state) && ParseEncoding(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <call-offset> ::= h <nv-offset> _
 //               ::= v <v-offset> _
 static bool ParseCallOffset(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'h') &&
-      ParseNVOffset(state) && ParseOneCharToken(state, '_')) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
+      ParseOneCharToken(state, '_')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOneCharToken(state, 'v') &&
-      ParseVOffset(state) && ParseOneCharToken(state, '_')) {
+  if (ParseOneCharToken(state, 'v') && ParseVOffset(state) &&
+      ParseOneCharToken(state, '_')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   return false;
 }
 
 // <nv-offset> ::= <(offset) number>
 static bool ParseNVOffset(State *state) {
-  return ParseNumber(state, NULL);
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  return ParseNumber(state, nullptr);
 }
 
 // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
 static bool ParseVOffset(State *state) {
-  State copy = *state;
-  if (ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
-      ParseNumber(state, NULL)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+      ParseNumber(state, nullptr)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <ctor-dtor-name> ::= C1 | C2 | C3
+// <ctor-dtor-name> ::= C1 | C2 | C3 | CI1 <base-class-type> | CI2
+// <base-class-type>
 //                  ::= D0 | D1 | D2
+// # GCC extensions: "unified" constructor/destructor.  See
+// #
+// https://github.com/gcc-mirror/gcc/blob/7ad17b583c3643bd4557f29b8391ca7ef08391f5/gcc/cp/mangle.c#L1847
+//                  ::= C4 | D4
 static bool ParseCtorDtorName(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'C') &&
-      ParseCharClass(state, "123")) {
-    const char * const prev_name = state->prev_name;
-    const ssize_t prev_name_length = state->prev_name_length;
-    MaybeAppendWithLength(state, prev_name, prev_name_length);
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  *state = copy;
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'C')) {
+    if (ParseCharClass(state, "1234")) {
+      const char* const prev_name =
+          state->out + state->parse_state.prev_name_idx;
+      MaybeAppendWithLength(state, prev_name,
+                            state->parse_state.prev_name_length);
+      return true;
+    } else if (ParseOneCharToken(state, 'I') && ParseCharClass(state, "12") &&
+               ParseClassEnumType(state)) {
+      return true;
+    }
+  }
+  state->parse_state = copy;
 
-  if (ParseOneCharToken(state, 'D') &&
-      ParseCharClass(state, "012")) {
-    const char * const prev_name = state->prev_name;
-    const ssize_t prev_name_length = state->prev_name_length;
+  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
+    const char* const prev_name = state->out + state->parse_state.prev_name_idx;
     MaybeAppend(state, "~");
-    MaybeAppendWithLength(state, prev_name, prev_name_length);
+    MaybeAppendWithLength(state, prev_name,
+                          state->parse_state.prev_name_length);
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+  return false;
+}
+
+// <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
+//                                   # member access (C++0x)
+//            ::= DT <expression> E  # decltype of an expression (C++0x)
+static bool ParseDecltype(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
@@ -921,67 +1255,93 @@
 //        ::= U <source-name> <type>  # vendor extended type qualifier
 //        ::= <builtin-type>
 //        ::= <function-type>
-//        ::= <class-enum-type>
+//        ::= <class-enum-type>  # note: just an alias for <name>
 //        ::= <array-type>
 //        ::= <pointer-to-member-type>
 //        ::= <template-template-param> <template-args>
 //        ::= <template-param>
+//        ::= <decltype>
 //        ::= <substitution>
 //        ::= Dp <type>          # pack expansion of (C++0x)
-//        ::= Dt <expression> E  # decltype of an id-expression or class
-//                               # member access (C++0x)
-//        ::= DT <expression> E  # decltype of an expression (C++0x)
+//        ::= Dv <num-elems> _   # GNU vector extension
 //
 static bool ParseType(State *state) {
-  // We should check CV-qualifers, and PRGC things first.
-  State copy = *state;
-  if (ParseCVQualifiers(state) && ParseType(state)) {
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  *state = copy;
+  ParseState copy = state->parse_state;
 
-  if (ParseCharClass(state, "OPRCG") && ParseType(state)) {
-    return true;
+  // We should check CV-qualifers, and PRGC things first.
+  //
+  // CV-qualifiers overlap with some operator names, but an operator name is not
+  // valid as a type.  To avoid an ambiguity that can lead to exponential time
+  // complexity, refuse to backtrack the CV-qualifiers.
+  //
+  // _Z4aoeuIrMvvE
+  //  => _Z 4aoeuI        rM  v     v   E
+  //         aoeu<operator%=, void, void>
+  //  => _Z 4aoeuI r Mv v              E
+  //         aoeu<void void::* restrict>
+  //
+  // By consuming the CV-qualifiers first, the former parse is disabled.
+  if (ParseCVQualifiers(state)) {
+    const bool result = ParseType(state);
+    if (!result) {
+      state->parse_state = copy;
+    }
+    return result;
   }
-  *state = copy;
+  state->parse_state = copy;
+
+  // Similarly, these tag characters can overlap with other <name>s resulting in
+  // two different parse prefixes that land on <template-args> in the same
+  // place, such as "C3r1xI...".  So, disable the "ctor-name = C3" parse by
+  // refusing to backtrack the tag characters.
+  if (ParseCharClass(state, "OPRCG")) {
+    const bool result = ParseType(state);
+    if (!result) {
+      state->parse_state = copy;
+    }
+    return result;
+  }
+  state->parse_state = copy;
 
   if (ParseTwoCharToken(state, "Dp") && ParseType(state)) {
     return true;
   }
-  *state = copy;
-
-  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
-      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
-    return true;
-  }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'U') && ParseSourceName(state) &&
       ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseBuiltinType(state) ||
-      ParseFunctionType(state) ||
-      ParseClassEnumType(state) ||
-      ParseArrayType(state) ||
-      ParsePointerToMemberType(state) ||
-      ParseSubstitution(state)) {
+  if (ParseBuiltinType(state) || ParseFunctionType(state) ||
+      ParseClassEnumType(state) || ParseArrayType(state) ||
+      ParsePointerToMemberType(state) || ParseDecltype(state) ||
+      // "std" on its own isn't a type.
+      ParseSubstitution(state, /*accept_std=*/false)) {
     return true;
   }
 
-  if (ParseTemplateTemplateParam(state) &&
-      ParseTemplateArgs(state)) {
+  if (ParseTemplateTemplateParam(state) && ParseTemplateArgs(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Less greedy than <template-template-param> <template-args>.
   if (ParseTemplateParam(state)) {
     return true;
   }
 
+  if (ParseTwoCharToken(state, "Dv") && ParseNumber(state, nullptr) &&
+      ParseOneCharToken(state, '_')) {
+    return true;
+  }
+  state->parse_state = copy;
+
   return false;
 }
 
@@ -989,6 +1349,10 @@
 // We don't allow empty <CV-qualifiers> to avoid infinite loop in
 // ParseType().
 static bool ParseCVQualifiers(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   int num_cv_qualifiers = 0;
   num_cv_qualifiers += ParseOneCharToken(state, 'r');
   num_cv_qualifiers += ParseOneCharToken(state, 'V');
@@ -996,208 +1360,529 @@
   return num_cv_qualifiers > 0;
 }
 
-// <builtin-type> ::= v, etc.
+// <builtin-type> ::= v, etc.  # single-character builtin types
 //                ::= u <source-name>
+//                ::= Dd, etc.  # two-character builtin types
+//
+// Not supported:
+//                ::= DF <number> _ # _FloatN (N bits)
+//
 static bool ParseBuiltinType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   const AbbrevPair *p;
-  for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) {
-    if (state->mangled_cur[0] == p->abbrev[0]) {
+  for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+    // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
+    if (p->abbrev[1] == '\0') {
+      if (ParseOneCharToken(state, p->abbrev[0])) {
+        MaybeAppend(state, p->real_name);
+        return true;
+      }
+    } else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
       MaybeAppend(state, p->real_name);
-      ++state->mangled_cur;
       return true;
     }
   }
 
-  State copy = *state;
+  ParseState copy = state->parse_state;
   if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <function-type> ::= F [Y] <bare-function-type> E
-static bool ParseFunctionType(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'F') &&
-      Optional(ParseOneCharToken(state, 'Y')) &&
-      ParseBareFunctionType(state) && ParseOneCharToken(state, 'E')) {
+//  <exception-spec> ::= Do                # non-throwing
+//                                           exception-specification (e.g.,
+//                                           noexcept, throw())
+//                   ::= DO <expression> E # computed (instantiation-dependent)
+//                                           noexcept
+//                   ::= Dw <type>+ E      # dynamic exception specification
+//                                           with instantiation-dependent types
+static bool ParseExceptionSpec(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (ParseTwoCharToken(state, "Do")) {
     return true;
   }
-  *state = copy;
+
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+  if (ParseTwoCharToken(state, "Dw") && OneOrMore(ParseType, state) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+static bool ParseFunctionType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
+      Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
+      Optional(ParseOneCharToken(state, 'O')) &&
+      ParseOneCharToken(state, 'E')) {
+    return true;
+  }
+  state->parse_state = copy;
   return false;
 }
 
 // <bare-function-type> ::= <(signature) type>+
 static bool ParseBareFunctionType(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   DisableAppend(state);
   if (OneOrMore(ParseType, state)) {
     RestoreAppend(state, copy.append);
     MaybeAppend(state, "()");
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <class-enum-type> ::= <name>
 static bool ParseClassEnumType(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   return ParseName(state);
 }
 
 // <array-type> ::= A <(positive dimension) number> _ <(element) type>
 //              ::= A [<(dimension) expression>] _ <(element) type>
 static bool ParseArrayType(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'A') && ParseNumber(state, NULL) &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
       ParseOneCharToken(state, '_') && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) &&
       ParseOneCharToken(state, '_') && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <pointer-to-member-type> ::= M <(class) type> <(member) type>
 static bool ParsePointerToMemberType(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'M') && ParseType(state) &&
-      ParseType(state)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <template-param> ::= T_
 //                  ::= T <parameter-2 non-negative number> _
 static bool ParseTemplateParam(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseTwoCharToken(state, "T_")) {
     MaybeAppend(state, "?");  // We don't support template substitutions.
     return true;
   }
 
-  State copy = *state;
-  if (ParseOneCharToken(state, 'T') && ParseNumber(state, NULL) &&
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
       ParseOneCharToken(state, '_')) {
     MaybeAppend(state, "?");  // We don't support template substitutions.
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-
 // <template-template-param> ::= <template-param>
 //                           ::= <substitution>
 static bool ParseTemplateTemplateParam(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   return (ParseTemplateParam(state) ||
-          ParseSubstitution(state));
+          // "std" on its own isn't a template.
+          ParseSubstitution(state, /*accept_std=*/false));
 }
 
 // <template-args> ::= I <template-arg>+ E
 static bool ParseTemplateArgs(State *state) {
-  State copy = *state;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
   DisableAppend(state);
-  if (ParseOneCharToken(state, 'I') &&
-      OneOrMore(ParseTemplateArg, state) &&
+  if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
       ParseOneCharToken(state, 'E')) {
     RestoreAppend(state, copy.append);
     MaybeAppend(state, "<>");
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <template-arg>  ::= <type>
 //                 ::= <expr-primary>
-//                 ::= I <template-arg>* E        # argument pack
 //                 ::= J <template-arg>* E        # argument pack
 //                 ::= X <expression> E
 static bool ParseTemplateArg(State *state) {
-  State copy = *state;
-  if ((ParseOneCharToken(state, 'I') || ParseOneCharToken(state, 'J')) &&
-      ZeroOrMore(ParseTemplateArg, state) &&
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
       ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseType(state) ||
-      ParseExprPrimary(state)) {
+  // There can be significant overlap between the following leading to
+  // exponential backtracking:
+  //
+  //   <expr-primary> ::= L <type> <expr-cast-value> E
+  //                 e.g. L 2xxIvE 1                 E
+  //   <type>         ==> <local-source-name> <template-args>
+  //                 e.g. L 2xx               IvE
+  //
+  // This means parsing an entire <type> twice, and <type> can contain
+  // <template-arg>, so this can generate exponential backtracking.  There is
+  // only overlap when the remaining input starts with "L <source-name>", so
+  // parse all cases that can start this way jointly to share the common prefix.
+  //
+  // We have:
+  //
+  //   <template-arg> ::= <type>
+  //                  ::= <expr-primary>
+  //
+  // First, drop all the productions of <type> that must start with something
+  // other than 'L'.  All that's left is <class-enum-type>; inline it.
+  //
+  //   <type> ::= <nested-name> # starts with 'N'
+  //          ::= <unscoped-name>
+  //          ::= <unscoped-template-name> <template-args>
+  //          ::= <local-name> # starts with 'Z'
+  //
+  // Drop and inline again:
+  //
+  //   <type> ::= <unscoped-name>
+  //          ::= <unscoped-name> <template-args>
+  //          ::= <substitution> <template-args> # starts with 'S'
+  //
+  // Merge the first two, inline <unscoped-name>, drop last:
+  //
+  //   <type> ::= <unqualified-name> [<template-args>]
+  //          ::= St <unqualified-name> [<template-args>] # starts with 'S'
+  //
+  // Drop and inline:
+  //
+  //   <type> ::= <operator-name> [<template-args>] # starts with lowercase
+  //          ::= <ctor-dtor-name> [<template-args>] # starts with 'C' or 'D'
+  //          ::= <source-name> [<template-args>] # starts with digit
+  //          ::= <local-source-name> [<template-args>]
+  //          ::= <unnamed-type-name> [<template-args>] # starts with 'U'
+  //
+  // One more time:
+  //
+  //   <type> ::= L <source-name> [<template-args>]
+  //
+  // Likewise with <expr-primary>:
+  //
+  //   <expr-primary> ::= L <type> <expr-cast-value> E
+  //                  ::= LZ <encoding> E # cannot overlap; drop
+  //                  ::= L <mangled_name> E # cannot overlap; drop
+  //
+  // By similar reasoning as shown above, the only <type>s starting with
+  // <source-name> are "<source-name> [<template-args>]".  Inline this.
+  //
+  //   <expr-primary> ::= L <source-name> [<template-args>] <expr-cast-value> E
+  //
+  // Now inline both of these into <template-arg>:
+  //
+  //   <template-arg> ::= L <source-name> [<template-args>]
+  //                  ::= L <source-name> [<template-args>] <expr-cast-value> E
+  //
+  // Merge them and we're done:
+  //   <template-arg>
+  //     ::= L <source-name> [<template-args>] [<expr-cast-value> E]
+  if (ParseLocalSourceName(state) && Optional(ParseTemplateArgs(state))) {
+    copy = state->parse_state;
+    if (ParseExprCastValue(state) && ParseOneCharToken(state, 'E')) {
+      return true;
+    }
+    state->parse_state = copy;
     return true;
   }
-  *state = copy;
+
+  // Now that the overlapping cases can't reach this code, we can safely call
+  // both of these.
+  if (ParseType(state) || ParseExprPrimary(state)) {
+    return true;
+  }
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'X') && ParseExpression(state) &&
       ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
-// <expression> ::= <template-param>
-//              ::= <expr-primary>
-//              ::= <unary operator-name> <expression>
-//              ::= <binary operator-name> <expression> <expression>
-//              ::= <trinary operator-name> <expression> <expression>
-//                  <expression>
+// <unresolved-type> ::= <template-param> [<template-args>]
+//                   ::= <decltype>
+//                   ::= <substitution>
+static inline bool ParseUnresolvedType(State* state) {
+  // No ComplexityGuard because we don't copy the state in this stack frame.
+  return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
+         ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
+}
+
+// <simple-id> ::= <source-name> [<template-args>]
+static inline bool ParseSimpleId(State* state) {
+  // No ComplexityGuard because we don't copy the state in this stack frame.
+
+  // Note: <simple-id> cannot be followed by a parameter pack; see comment in
+  // ParseUnresolvedType.
+  return ParseSourceName(state) && Optional(ParseTemplateArgs(state));
+}
+
+// <base-unresolved-name> ::= <source-name> [<template-args>]
+//                        ::= on <operator-name> [<template-args>]
+//                        ::= dn <destructor-name>
+static bool ParseBaseUnresolvedName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (ParseSimpleId(state)) {
+    return true;
+  }
+
+  ParseState copy = state->parse_state;
+  if (ParseTwoCharToken(state, "on") && ParseOperatorName(state, nullptr) &&
+      Optional(ParseTemplateArgs(state))) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "dn") &&
+      (ParseUnresolvedType(state) || ParseSimpleId(state))) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <unresolved-name> ::= [gs] <base-unresolved-name>
+//                   ::= sr <unresolved-type> <base-unresolved-name>
+//                   ::= srN <unresolved-type> <unresolved-qualifier-level>+ E
+//                         <base-unresolved-name>
+//                   ::= [gs] sr <unresolved-qualifier-level>+ E
+//                         <base-unresolved-name>
+static bool ParseUnresolvedName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  ParseState copy = state->parse_state;
+  if (Optional(ParseTwoCharToken(state, "gs")) &&
+      ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "sr") && ParseUnresolvedType(state) &&
+      ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (ParseTwoCharToken(state, "sr") && ParseOneCharToken(state, 'N') &&
+      ParseUnresolvedType(state) &&
+      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  if (Optional(ParseTwoCharToken(state, "gs")) &&
+      ParseTwoCharToken(state, "sr") &&
+      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
+      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <expression> ::= <1-ary operator-name> <expression>
+//              ::= <2-ary operator-name> <expression> <expression>
+//              ::= <3-ary operator-name> <expression> <expression> <expression>
+//              ::= cl <expression>+ E
+//              ::= cp <simple-id> <expression>* E # Clang-specific.
+//              ::= cv <type> <expression>      # type (expression)
+//              ::= cv <type> _ <expression>* E # type (expr-list)
 //              ::= st <type>
+//              ::= <template-param>
+//              ::= <function-param>
+//              ::= <expr-primary>
+//              ::= dt <expression> <unresolved-name> # expr.name
+//              ::= pt <expression> <unresolved-name> # expr->name
+//              ::= sp <expression>         # argument pack expansion
 //              ::= sr <type> <unqualified-name> <template-args>
 //              ::= sr <type> <unqualified-name>
+// <function-param> ::= fp <(top-level) CV-qualifiers> _
+//                  ::= fp <(top-level) CV-qualifiers> <number> _
+//                  ::= fL <number> p <(top-level) CV-qualifiers> _
+//                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
 static bool ParseExpression(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
     return true;
   }
 
-  State copy = *state;
-  if (ParseOperatorName(state) &&
-      ParseExpression(state) &&
-      ParseExpression(state) &&
-      ParseExpression(state)) {
+  ParseState copy = state->parse_state;
+
+  // Object/function call expression.
+  if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
+      ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOperatorName(state) &&
-      ParseExpression(state) &&
-      ParseExpression(state)) {
+  // Clang-specific "cp <simple-id> <expression>* E"
+  //   https://clang.llvm.org/doxygen/ItaniumMangle_8cpp_source.html#l04338
+  if (ParseTwoCharToken(state, "cp") && ParseSimpleId(state) &&
+      ZeroOrMore(ParseExpression, state) && ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOperatorName(state) &&
-      ParseExpression(state)) {
+  // Function-param expression (level 0).
+  if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
+      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
+  // Function-param expression (level 1+).
+  if (ParseTwoCharToken(state, "fL") && Optional(ParseNumber(state, nullptr)) &&
+      ParseOneCharToken(state, 'p') && Optional(ParseCVQualifiers(state)) &&
+      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  // Parse the conversion expressions jointly to avoid re-parsing the <type> in
+  // their common prefix.  Parsed as:
+  // <expression> ::= cv <type> <conversion-args>
+  // <conversion-args> ::= _ <expression>* E
+  //                   ::= <expression>
+  //
+  // Also don't try ParseOperatorName after seeing "cv", since ParseOperatorName
+  // also needs to accept "cv <type>" in other contexts.
+  if (ParseTwoCharToken(state, "cv")) {
+    if (ParseType(state)) {
+      ParseState copy2 = state->parse_state;
+      if (ParseOneCharToken(state, '_') && ZeroOrMore(ParseExpression, state) &&
+          ParseOneCharToken(state, 'E')) {
+        return true;
+      }
+      state->parse_state = copy2;
+      if (ParseExpression(state)) {
+        return true;
+      }
+    }
+  } else {
+    // Parse unary, binary, and ternary operator expressions jointly, taking
+    // care not to re-parse subexpressions repeatedly. Parse like:
+    //   <expression> ::= <operator-name> <expression>
+    //                    [<one-to-two-expressions>]
+    //   <one-to-two-expressions> ::= <expression> [<expression>]
+    int arity = -1;
+    if (ParseOperatorName(state, &arity) &&
+        arity > 0 &&  // 0 arity => disabled.
+        (arity < 3 || ParseExpression(state)) &&
+        (arity < 2 || ParseExpression(state)) &&
+        (arity < 1 || ParseExpression(state))) {
+      return true;
+    }
+  }
+  state->parse_state = copy;
+
+  // sizeof type
   if (ParseTwoCharToken(state, "st") && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
-      ParseUnqualifiedName(state) &&
-      ParseTemplateArgs(state)) {
+  // Object and pointer member access expressions.
+  if ((ParseTwoCharToken(state, "dt") || ParseTwoCharToken(state, "pt")) &&
+      ParseExpression(state) && ParseType(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
-      ParseUnqualifiedName(state)) {
+  // Pointer-to-member access expressions.  This parses the same as a binary
+  // operator, but it's implemented separately because "ds" shouldn't be
+  // accepted in other contexts that parse an operator name.
+  if (ParseTwoCharToken(state, "ds") && ParseExpression(state) &&
+      ParseExpression(state)) {
     return true;
   }
-  *state = copy;
-  return false;
+  state->parse_state = copy;
+
+  // Parameter pack expansion
+  if (ParseTwoCharToken(state, "sp") && ParseExpression(state)) {
+    return true;
+  }
+  state->parse_state = copy;
+
+  return ParseUnresolvedName(state);
 }
 
 // <expr-primary> ::= L <type> <(value) number> E
@@ -1205,116 +1890,208 @@
 //                ::= L <mangled-name> E
 //                // A bug in g++'s C++ ABI version 2 (-fabi-version=2).
 //                ::= LZ <encoding> E
+//
+// Warning, subtle: the "bug" LZ production above is ambiguous with the first
+// production where <type> starts with <local-name>, which can lead to
+// exponential backtracking in two scenarios:
+//
+// - When whatever follows the E in the <local-name> in the first production is
+//   not a name, we backtrack the whole <encoding> and re-parse the whole thing.
+//
+// - When whatever follows the <local-name> in the first production is not a
+//   number and this <expr-primary> may be followed by a name, we backtrack the
+//   <name> and re-parse it.
+//
+// Moreover this ambiguity isn't always resolved -- for example, the following
+// has two different parses:
+//
+//   _ZaaILZ4aoeuE1x1EvE
+//   => operator&&<aoeu, x, E, void>
+//   => operator&&<(aoeu::x)(1), void>
+//
+// To resolve this, we just do what GCC's demangler does, and refuse to parse
+// casts to <local-name> types.
 static bool ParseExprPrimary(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'L') && ParseType(state) &&
-      ParseNumber(state, NULL) &&
-      ParseOneCharToken(state, 'E')) {
-    return true;
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
   }
-  *state = copy;
+  ParseState copy = state->parse_state;
 
+  // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
+  // or fail, no backtracking.
+  if (ParseTwoCharToken(state, "LZ")) {
+    if (ParseEncoding(state) && ParseOneCharToken(state, 'E')) {
+      return true;
+    }
+
+    state->parse_state = copy;
+    return false;
+  }
+
+  // The merged cast production.
   if (ParseOneCharToken(state, 'L') && ParseType(state) &&
-      ParseFloatNumber(state) &&
-      ParseOneCharToken(state, 'E')) {
+      ParseExprCastValue(state)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   if (ParseOneCharToken(state, 'L') && ParseMangledName(state) &&
       ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
-
-  if (ParseTwoCharToken(state, "LZ") && ParseEncoding(state) &&
-      ParseOneCharToken(state, 'E')) {
-    return true;
-  }
-  *state = copy;
+  state->parse_state = copy;
 
   return false;
 }
 
-// <local-name> := Z <(function) encoding> E <(entity) name>
-//                 [<discriminator>]
-//              := Z <(function) encoding> E s [<discriminator>]
-static bool ParseLocalName(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
-      ParseOneCharToken(state, 'E') && MaybeAppend(state, "::") &&
-      ParseName(state) && Optional(ParseDiscriminator(state))) {
+// <number> or <float>, followed by 'E', as described above ParseExprPrimary.
+static bool ParseExprCastValue(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  // We have to be able to backtrack after accepting a number because we could
+  // have e.g. "7fffE", which will accept "7" as a number but then fail to find
+  // the 'E'.
+  ParseState copy = state->parse_state;
+  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
-  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
-      ParseTwoCharToken(state, "Es") && Optional(ParseDiscriminator(state))) {
+  if (ParseFloatNumber(state) && ParseOneCharToken(state, 'E')) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
+
+  return false;
+}
+
+// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
+//              ::= Z <(function) encoding> E s [<discriminator>]
+//
+// Parsing a common prefix of these two productions together avoids an
+// exponential blowup of backtracking.  Parse like:
+//   <local-name> := Z <encoding> E <local-name-suffix>
+//   <local-name-suffix> ::= s [<discriminator>]
+//                       ::= <name> [<discriminator>]
+
+static bool ParseLocalNameSuffix(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+
+  if (MaybeAppend(state, "::") && ParseName(state) &&
+      Optional(ParseDiscriminator(state))) {
+    return true;
+  }
+
+  // Since we're not going to overwrite the above "::" by re-parsing the
+  // <encoding> (whose trailing '\0' byte was in the byte now holding the
+  // first ':'), we have to rollback the "::" if the <name> parse failed.
+  if (state->parse_state.append) {
+    state->out[state->parse_state.out_cur_idx - 2] = '\0';
+  }
+
+  return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
+}
+
+static bool ParseLocalName(State* state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+      ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+    return true;
+  }
+  state->parse_state = copy;
   return false;
 }
 
 // <discriminator> := _ <(non-negative) number>
 static bool ParseDiscriminator(State *state) {
-  State copy = *state;
-  if (ParseOneCharToken(state, '_') && ParseNumber(state, NULL)) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
+  ParseState copy = state->parse_state;
+  if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // <substitution> ::= S_
 //                ::= S <seq-id> _
 //                ::= St, etc.
-static bool ParseSubstitution(State *state) {
+//
+// "St" is special in that it's not valid as a standalone name, and it *is*
+// allowed to precede a name without being wrapped in "N...E".  This means that
+// if we accept it on its own, we can accept "St1a" and try to parse
+// template-args, then fail and backtrack, accept "St" on its own, then "1a" as
+// an unqualified name and re-parse the same template-args.  To block this
+// exponential backtracking, we disable it with 'accept_std=false' in
+// problematic contexts.
+static bool ParseSubstitution(State* state, bool accept_std) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseTwoCharToken(state, "S_")) {
     MaybeAppend(state, "?");  // We don't support substitutions.
     return true;
   }
 
-  State copy = *state;
+  ParseState copy = state->parse_state;
   if (ParseOneCharToken(state, 'S') && ParseSeqId(state) &&
       ParseOneCharToken(state, '_')) {
     MaybeAppend(state, "?");  // We don't support substitutions.
     return true;
   }
-  *state = copy;
+  state->parse_state = copy;
 
   // Expand abbreviations like "St" => "std".
   if (ParseOneCharToken(state, 'S')) {
     const AbbrevPair *p;
-    for (p = kSubstitutionList; p->abbrev != NULL; ++p) {
-      if (state->mangled_cur[0] == p->abbrev[1]) {
+    for (p = kSubstitutionList; p->abbrev != nullptr; ++p) {
+      if (RemainingInput(state)[0] == p->abbrev[1] &&
+          (accept_std || p->abbrev[1] != 't')) {
         MaybeAppend(state, "std");
         if (p->real_name[0] != '\0') {
           MaybeAppend(state, "::");
           MaybeAppend(state, p->real_name);
         }
-        ++state->mangled_cur;
+        ++state->parse_state.mangled_idx;
         return true;
       }
     }
   }
-  *state = copy;
+  state->parse_state = copy;
   return false;
 }
 
 // Parse <mangled-name>, optionally followed by either a function-clone suffix
 // or version suffix.  Returns true only if all of "mangled_cur" was consumed.
 static bool ParseTopLevelMangledName(State *state) {
+  ComplexityGuard guard(state);
+  if (guard.IsTooComplex()) {
+    return false;
+  }
   if (ParseMangledName(state)) {
-    if (state->mangled_cur[0] != '\0') {
+    if (RemainingInput(state)[0] != '\0') {
       // Drop trailing function clone suffix, if any.
-      if (IsFunctionCloneSuffix(state->mangled_cur)) {
+      if (IsFunctionCloneSuffix(RemainingInput(state))) {
         return true;
       }
       // Append trailing version suffix if any.
       // ex. _Z3foo@@GLIBCXX_3.4
-      if (state->mangled_cur[0] == '@') {
-        MaybeAppend(state, state->mangled_cur);
+      if (RemainingInput(state)[0] == '@') {
+        MaybeAppend(state, RemainingInput(state));
         return true;
       }
       return false;  // Unconsumed suffix.
@@ -1323,6 +2100,10 @@
   }
   return false;
 }
+
+static bool Overflowed(const State* state) {
+  return state->parse_state.out_cur_idx >= state->out_end_idx;
+}
 #endif
 
 // The demangler entry point.
@@ -1359,7 +2140,8 @@
 #else
   State state;
   InitState(&state, mangled, out, out_size);
-  return ParseTopLevelMangledName(&state) && !state.overflowed;
+  return ParseTopLevelMangledName(&state) && !Overflowed(&state) &&
+         state.parse_state.out_cur_idx > 0;
 #endif
 }
 
diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
index 46200c8..7d5cfaa 100644
--- a/base/third_party/symbolize/demangle.h
+++ b/base/third_party/symbolize/demangle.h
@@ -70,6 +70,8 @@
 #ifndef BASE_DEMANGLE_H_
 #define BASE_DEMANGLE_H_
 
+#include <stddef.h>
+
 #include "config.h"
 #include "glog/logging.h"
 
diff --git a/base/third_party/symbolize/patches/009-clang-format.patch b/base/third_party/symbolize/patches/009-clang-format.patch
deleted file mode 100644
index 1ae3ab7..0000000
--- a/base/third_party/symbolize/patches/009-clang-format.patch
+++ /dev/null
@@ -1,382 +0,0 @@
-diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
-index 9276c5b879a8c..da4cbf6aeffa5 100644
---- a/base/third_party/symbolize/demangle.cc
-+++ b/base/third_party/symbolize/demangle.cc
-@@ -149,11 +149,11 @@ static const AbbrevPair kSubstitutionList[] = {
- 
- // State needed for demangling.
- typedef struct {
--  const char *mangled_cur;   // Cursor of mangled name.
--  char *out_cur;             // Cursor of output string.
--  const char *out_begin;     // Beginning of output string.
--  const char *out_end;       // End of output string.
--  const char *prev_name;     // For constructors/destructors.
-+  const char* mangled_cur;   // Cursor of mangled name.
-+  char* out_cur;             // Cursor of output string.
-+  const char* out_begin;     // Beginning of output string.
-+  const char* out_end;       // End of output string.
-+  const char* prev_name;     // For constructors/destructors.
-   ssize_t prev_name_length;  // For constructors/destructors.
-   short nest_level;          // For nested names.
-   bool append;               // Append flag.
-@@ -172,7 +172,7 @@ static size_t StrLen(const char *str) {
- }
- 
- // Returns true if "str" has at least "n" characters remaining.
--static bool AtLeastNumCharsRemaining(const char *str, ssize_t n) {
-+static bool AtLeastNumCharsRemaining(const char* str, ssize_t n) {
-   for (ssize_t i = 0; i < n; ++i) {
-     if (str[i] == '\0') {
-       return false;
-@@ -191,8 +191,10 @@ static bool StrPrefix(const char *str, const char *prefix) {
-   return prefix[i] == '\0';  // Consumed everything in "prefix".
- }
- 
--static void InitState(State *state, const char *mangled,
--                      char *out, size_t out_size) {
-+static void InitState(State* state,
-+                      const char* mangled,
-+                      char* out,
-+                      size_t out_size) {
-   state->mangled_cur = mangled;
-   state->out_cur = out;
-   state->out_begin = out;
-@@ -269,7 +271,7 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) {
- // Append "str" at "out_cur".  If there is an overflow, "overflowed"
- // is set to true for later use.  The output string is ensured to
- // always terminate with '\0' as long as there is no overflow.
--static void Append(State *state, const char * const str, ssize_t length) {
-+static void Append(State* state, const char* const str, ssize_t length) {
-   for (ssize_t i = 0; i < length; ++i) {
-     if (state->out_cur + 1 < state->out_end) {  // +1 for '\0'
-       *state->out_cur = str[i];
-@@ -325,7 +327,8 @@ static bool IsFunctionCloneSuffix(const char *str) {
- 
- // Append "str" with some tweaks, iff "append" state is true.
- // Returns true so that it can be placed in "if" conditions.
--static void MaybeAppendWithLength(State *state, const char * const str,
-+static void MaybeAppendWithLength(State* state,
-+                                  const char* const str,
-                                   ssize_t length) {
-   if (state->append && length > 0) {
-     // Append a space if the output buffer ends with '<' and "str"
-@@ -401,7 +404,7 @@ static void MaybeCancelLastSeparator(State *state) {
- 
- // Returns true if the identifier of the given length pointed to by
- // "mangled_cur" is anonymous namespace.
--static bool IdentifierIsAnonymousNamespace(State *state, ssize_t length) {
-+static bool IdentifierIsAnonymousNamespace(State* state, ssize_t length) {
-   static const char anon_prefix[] = "_GLOBAL__N_";
-   return (length > static_cast<ssize_t>(sizeof(anon_prefix)) -
-                        1 &&  // Should be longer.
-@@ -422,7 +425,7 @@ static bool ParseLocalSourceName(State *state);
- static bool ParseNumber(State *state, int *number_out);
- static bool ParseFloatNumber(State *state);
- static bool ParseSeqId(State *state);
--static bool ParseIdentifier(State *state, ssize_t length);
-+static bool ParseIdentifier(State* state, ssize_t length);
- static bool ParseAbiTags(State *state);
- static bool ParseAbiTag(State *state);
- static bool ParseOperatorName(State *state);
-@@ -691,7 +694,7 @@ static bool ParseSeqId(State *state) {
- }
- 
- // <identifier> ::= <unqualified source code identifier> (of given length)
--static bool ParseIdentifier(State *state, ssize_t length) {
-+static bool ParseIdentifier(State* state, ssize_t length) {
-   if (length == -1 ||
-       !AtLeastNumCharsRemaining(state->mangled_cur, length)) {
-     return false;
-@@ -1323,7 +1326,7 @@ static bool ParseTopLevelMangledName(State *state) {
- #endif
- 
- // The demangler entry point.
--bool Demangle(const char *mangled, char *out, size_t out_size) {
-+bool Demangle(const char* mangled, char* out, size_t out_size) {
- #if defined(GLOG_OS_WINDOWS)
- #if defined(HAVE_DBGHELP)
-   // When built with incremental linking, the Windows debugger
-diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
-index 416f7ee153560..46200c8387d82 100644
---- a/base/third_party/symbolize/demangle.h
-+++ b/base/third_party/symbolize/demangle.h
-@@ -78,7 +78,7 @@ _START_GOOGLE_NAMESPACE_
- // Demangle "mangled".  On success, return true and write the
- // demangled symbol name to "out".  Otherwise, return false.
- // "out" is modified even if demangling is unsuccessful.
--bool GLOG_EXPORT Demangle(const char *mangled, char *out, size_t out_size);
-+bool GLOG_EXPORT Demangle(const char* mangled, char* out, size_t out_size);
- 
- _END_GOOGLE_NAMESPACE_
- 
-diff --git a/base/third_party/symbolize/glog/logging.h b/base/third_party/symbolize/glog/logging.h
-index 46869226024da..b935e3ec9cded 100644
---- a/base/third_party/symbolize/glog/logging.h
-+++ b/base/third_party/symbolize/glog/logging.h
-@@ -38,4 +38,4 @@
- 
- // Not needed in Chrome.
- 
--#endif // GLOG_LOGGING_H
-+#endif  // GLOG_LOGGING_H
-diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
-index b6ddc85d57185..a3b8399f411bf 100644
---- a/base/third_party/symbolize/symbolize.cc
-+++ b/base/third_party/symbolize/symbolize.cc
-@@ -97,7 +97,7 @@ void InstallSymbolizeOpenObjectFileCallback(
- // where the input symbol is demangled in-place.
- // To keep stack consumption low, we would like this function to not
- // get inlined.
--static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, size_t out_size) {
-+static ATTRIBUTE_NOINLINE void DemangleInplace(char* out, size_t out_size) {
-   char demangled[256];  // Big enough for sane demangled symbols.
-   if (Demangle(out, demangled, sizeof(demangled))) {
-     // Demangling succeeded. Copy to out if the space allows.
-@@ -121,17 +121,17 @@ _END_GOOGLE_NAMESPACE_
- #else
- #include <elf.h>
- #endif
-+#include <fcntl.h>
-+#include <stdint.h>
-+#include <sys/stat.h>
-+#include <sys/types.h>
-+#include <unistd.h>
- #include <cerrno>
- #include <climits>
- #include <cstddef>
- #include <cstdio>
- #include <cstdlib>
- #include <cstring>
--#include <fcntl.h>
--#include <stdint.h>
--#include <sys/stat.h>
--#include <sys/types.h>
--#include <unistd.h>
- 
- #include "symbolize.h"
- #include "config.h"
-@@ -153,7 +153,8 @@ ssize_t ReadFromOffset(const int fd,
-                        const size_t count,
-                        const size_t offset) {
-   SAFE_ASSERT(fd >= 0);
--  SAFE_ASSERT(count <= static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
-+  SAFE_ASSERT(count <=
-+              static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
-   char *buf0 = reinterpret_cast<char *>(buf);
-   size_t num_bytes = 0;
-   while (num_bytes < count) {
-@@ -176,8 +177,10 @@ ssize_t ReadFromOffset(const int fd,
- // pointed by "fd" into the buffer starting at "buf" while handling
- // short reads and EINTR.  On success, return true. Otherwise, return
- // false.
--static bool ReadFromOffsetExact(const int fd, void *buf,
--                                const size_t count, const size_t offset) {
-+static bool ReadFromOffsetExact(const int fd,
-+                                void* buf,
-+                                const size_t count,
-+                                const size_t offset) {
-   ssize_t len = ReadFromOffset(fd, buf, count, offset);
-   return static_cast<size_t>(len) == count;
- }
-@@ -199,9 +202,11 @@ static int FileGetElfType(const int fd) {
- // and return true.  Otherwise, return false.
- // To keep stack consumption low, we would like this function to not get
- // inlined.
--static ATTRIBUTE_NOINLINE bool
--GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const size_t sh_offset,
--                       ElfW(Word) type, ElfW(Shdr) *out) {
-+static ATTRIBUTE_NOINLINE bool GetSectionHeaderByType(const int fd,
-+                                                      ElfW(Half) sh_num,
-+                                                      const size_t sh_offset,
-+                                                      ElfW(Word) type,
-+                                                      ElfW(Shdr) * out) {
-   // Read at most 16 section headers at a time to save read calls.
-   ElfW(Shdr) buf[16];
-   for (size_t i = 0; i < sh_num;) {
-@@ -248,8 +253,8 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
-   }
- 
-   for (size_t i = 0; i < elf_header.e_shnum; ++i) {
--    size_t section_header_offset = (elf_header.e_shoff +
--                                   elf_header.e_shentsize * i);
-+    size_t section_header_offset =
-+        (elf_header.e_shoff + elf_header.e_shentsize * i);
-     if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) {
-       return false;
-     }
-@@ -281,10 +286,13 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
- // to out.  Otherwise, return false.
- // To keep stack consumption low, we would like this function to not get
- // inlined.
--static ATTRIBUTE_NOINLINE bool
--FindSymbol(uint64_t pc, const int fd, char *out, size_t out_size,
--           uint64_t symbol_offset, const ElfW(Shdr) *strtab,
--           const ElfW(Shdr) *symtab) {
-+static ATTRIBUTE_NOINLINE bool FindSymbol(uint64_t pc,
-+                                          const int fd,
-+                                          char* out,
-+                                          size_t out_size,
-+                                          uint64_t symbol_offset,
-+                                          const ElfW(Shdr) * strtab,
-+                                          const ElfW(Shdr) * symtab) {
-   if (symtab == NULL) {
-     return false;
-   }
-@@ -384,7 +392,7 @@ namespace {
- // and snprintf().
- class LineReader {
-  public:
--  explicit LineReader(int fd, char *buf, size_t buf_len, size_t offset)
-+  explicit LineReader(int fd, char* buf, size_t buf_len, size_t offset)
-       : fd_(fd),
-         buf_(buf),
-         buf_len_(buf_len),
-@@ -449,11 +457,12 @@ class LineReader {
-   }
- 
-  private:
--  LineReader(const LineReader &);
-+  LineReader(const LineReader&);
-   void operator=(const LineReader&);
- 
-   char *FindLineFeed() {
--    return reinterpret_cast<char *>(memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
-+    return reinterpret_cast<char*>(
-+        memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
-   }
- 
-   bool BufferIsEmpty() {
-@@ -483,7 +492,8 @@ static char *GetHex(const char *start, const char *end, uint64_t *hex) {
-     int ch = *p;
-     if ((ch >= '0' && ch <= '9') ||
-         (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) {
--      *hex = (*hex << 4U) | (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
-+      *hex = (*hex << 4U) |
-+             (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
-     } else {  // Encountered the first non-hex character.
-       break;
-     }
-@@ -647,12 +657,11 @@ static int OpenObjectFileContainingPcAndGetStartAddressNoHook(
-   }
- }
- 
--int OpenObjectFileContainingPcAndGetStartAddress(
--    uint64_t pc,
--    uint64_t& start_address,
--    uint64_t& base_address,
--    char* out_file_name,
--    size_t out_file_name_size) {
-+int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
-+                                                 uint64_t& start_address,
-+                                                 uint64_t& base_address,
-+                                                 char* out_file_name,
-+                                                 size_t out_file_name_size) {
-   if (g_symbolize_open_object_file_callback) {
-     return g_symbolize_open_object_file_callback(
-         pc, start_address, base_address, out_file_name, out_file_name_size);
-@@ -668,7 +677,11 @@ int OpenObjectFileContainingPcAndGetStartAddress(
- // bytes. Output will be truncated as needed, and a NUL character is always
- // appended.
- // NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
--static char *itoa_r(uintptr_t i, char *buf, size_t sz, unsigned base, size_t padding) {
-+static char* itoa_r(uintptr_t i,
-+                    char* buf,
-+                    size_t sz,
-+                    unsigned base,
-+                    size_t padding) {
-   // Make sure we can write at least one NUL byte.
-   size_t n = 1;
-   if (n > sz) {
-@@ -745,7 +758,8 @@ static void SafeAppendHexNumber(uint64_t value, char* dest, size_t dest_size) {
- // and "out" is used as its output.
- // To keep stack consumption low, we would like this function to not
- // get inlined.
--static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
-+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
-+                                                    char* out,
-                                                     size_t out_size) {
-   uint64_t pc0 = reinterpret_cast<uintptr_t>(pc);
-   uint64_t start_address = 0;
-@@ -822,14 +836,16 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
- 
- _END_GOOGLE_NAMESPACE_
- 
--#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && defined(HAVE_DLADDR)
-+#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && \
-+    defined(HAVE_DLADDR)
- 
- #include <dlfcn.h>
- #include <cstring>
- 
- _START_GOOGLE_NAMESPACE_
- 
--static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
-+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
-+                                                    char* out,
-                                                     size_t out_size) {
-   Dl_info info;
-   if (dladdr(pc, &info)) {
-@@ -883,7 +899,8 @@ private:
-   SymInitializer& operator=(const SymInitializer&);
- };
- 
--static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
-+static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
-+                                                    char* out,
-                                                     size_t out_size) {
-   const static SymInitializer symInitializer;
-   if (!symInitializer.ready) {
-@@ -918,7 +935,7 @@ _END_GOOGLE_NAMESPACE_
- 
- _START_GOOGLE_NAMESPACE_
- 
--bool Symbolize(void *pc, char *out, size_t out_size) {
-+bool Symbolize(void* pc, char* out, size_t out_size) {
-   return SymbolizeAndDemangle(pc, out, out_size);
- }
- 
-diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
-index 64ff0509ce57d..987569fdde67f 100644
---- a/base/third_party/symbolize/symbolize.h
-+++ b/base/third_party/symbolize/symbolize.h
-@@ -127,7 +127,7 @@ ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
- 
- _END_GOOGLE_NAMESPACE_
- 
--#endif  /* __ELF__ */
-+#endif /* __ELF__ */
- 
- _START_GOOGLE_NAMESPACE_
- 
-@@ -140,7 +140,7 @@ struct FileDescriptor {
-   int get() { return fd_; }
- 
-  private:
--  FileDescriptor(const FileDescriptor &);
-+  FileDescriptor(const FileDescriptor&);
-   void operator=(const FileDescriptor&);
- };
- 
-diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
-index 8c61380fad159..bb206a8020315 100644
---- a/base/third_party/symbolize/utilities.h
-+++ b/base/third_party/symbolize/utilities.h
-@@ -35,13 +35,13 @@
- #define UTILITIES_H__
- 
- #ifdef HAVE___ATTRIBUTE__
--# define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
--# define HAVE_ATTRIBUTE_NOINLINE
-+#define ATTRIBUTE_NOINLINE __attribute__((noinline))
-+#define HAVE_ATTRIBUTE_NOINLINE
- #elif defined(GLOG_OS_WINDOWS)
--# define ATTRIBUTE_NOINLINE __declspec(noinline)
--# define HAVE_ATTRIBUTE_NOINLINE
-+#define ATTRIBUTE_NOINLINE __declspec(noinline)
-+#define HAVE_ATTRIBUTE_NOINLINE
- #else
--# define ATTRIBUTE_NOINLINE
-+#define ATTRIBUTE_NOINLINE
- #endif
- 
- #endif  // UTILITIES_H__
diff --git a/base/third_party/symbolize/patches/009-clone-absl-demangle.patch b/base/third_party/symbolize/patches/009-clone-absl-demangle.patch
new file mode 100644
index 0000000..3adae98
--- /dev/null
+++ b/base/third_party/symbolize/patches/009-clone-absl-demangle.patch
@@ -0,0 +1,2388 @@
+diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
+index 9276c5b879a8c..2632646dd4072 100644
+--- a/base/third_party/symbolize/demangle.cc
++++ b/base/third_party/symbolize/demangle.cc
+@@ -34,13 +34,14 @@
+ //
+ // Note that we only have partial C++0x support yet.
+ 
+-#include <cstdio>  // for NULL
+-
+ #include "demangle.h"
+-#include "utilities.h"
+ 
+ #if defined(GLOG_OS_WINDOWS)
+ #include <dbghelp.h>
++#else
++#include <cstdint>
++#include <cstdio>
++#include <limits>
+ #endif
+ 
+ _START_GOOGLE_NAMESPACE_
+@@ -49,117 +50,199 @@ _START_GOOGLE_NAMESPACE_
+ typedef struct {
+   const char *abbrev;
+   const char *real_name;
++  // Number of arguments in <expression> context, or 0 if disallowed.
++  int arity;
+ } AbbrevPair;
+ 
+ // List of operators from Itanium C++ ABI.
+ static const AbbrevPair kOperatorList[] = {
+-  { "nw", "new" },
+-  { "na", "new[]" },
+-  { "dl", "delete" },
+-  { "da", "delete[]" },
+-  { "ps", "+" },
+-  { "ng", "-" },
+-  { "ad", "&" },
+-  { "de", "*" },
+-  { "co", "~" },
+-  { "pl", "+" },
+-  { "mi", "-" },
+-  { "ml", "*" },
+-  { "dv", "/" },
+-  { "rm", "%" },
+-  { "an", "&" },
+-  { "or", "|" },
+-  { "eo", "^" },
+-  { "aS", "=" },
+-  { "pL", "+=" },
+-  { "mI", "-=" },
+-  { "mL", "*=" },
+-  { "dV", "/=" },
+-  { "rM", "%=" },
+-  { "aN", "&=" },
+-  { "oR", "|=" },
+-  { "eO", "^=" },
+-  { "ls", "<<" },
+-  { "rs", ">>" },
+-  { "lS", "<<=" },
+-  { "rS", ">>=" },
+-  { "eq", "==" },
+-  { "ne", "!=" },
+-  { "lt", "<" },
+-  { "gt", ">" },
+-  { "le", "<=" },
+-  { "ge", ">=" },
+-  { "nt", "!" },
+-  { "aa", "&&" },
+-  { "oo", "||" },
+-  { "pp", "++" },
+-  { "mm", "--" },
+-  { "cm", "," },
+-  { "pm", "->*" },
+-  { "pt", "->" },
+-  { "cl", "()" },
+-  { "ix", "[]" },
+-  { "qu", "?" },
+-  { "st", "sizeof" },
+-  { "sz", "sizeof" },
+-  { NULL, NULL },
++    // New has special syntax (not currently supported).
++    {"nw", "new", 0},
++    {"na", "new[]", 0},
++
++    // Works except that the 'gs' prefix is not supported.
++    {"dl", "delete", 1},
++    {"da", "delete[]", 1},
++
++    {"ps", "+", 1},  // "positive"
++    {"ng", "-", 1},  // "negative"
++    {"ad", "&", 1},  // "address-of"
++    {"de", "*", 1},  // "dereference"
++    {"co", "~", 1},
++
++    {"pl", "+", 2},
++    {"mi", "-", 2},
++    {"ml", "*", 2},
++    {"dv", "/", 2},
++    {"rm", "%", 2},
++    {"an", "&", 2},
++    {"or", "|", 2},
++    {"eo", "^", 2},
++    {"aS", "=", 2},
++    {"pL", "+=", 2},
++    {"mI", "-=", 2},
++    {"mL", "*=", 2},
++    {"dV", "/=", 2},
++    {"rM", "%=", 2},
++    {"aN", "&=", 2},
++    {"oR", "|=", 2},
++    {"eO", "^=", 2},
++    {"ls", "<<", 2},
++    {"rs", ">>", 2},
++    {"lS", "<<=", 2},
++    {"rS", ">>=", 2},
++    {"eq", "==", 2},
++    {"ne", "!=", 2},
++    {"lt", "<", 2},
++    {"gt", ">", 2},
++    {"le", "<=", 2},
++    {"ge", ">=", 2},
++    {"nt", "!", 1},
++    {"aa", "&&", 2},
++    {"oo", "||", 2},
++    {"pp", "++", 1},
++    {"mm", "--", 1},
++    {"cm", ",", 2},
++    {"pm", "->*", 2},
++    {"pt", "->", 0},  // Special syntax
++    {"cl", "()", 0},  // Special syntax
++    {"ix", "[]", 2},
++    {"qu", "?", 3},
++    {"st", "sizeof", 0},  // Special syntax
++    {"sz", "sizeof", 1},  // Not a real operator name, but used in expressions.
++    {nullptr, nullptr, 0},
+ };
+ 
+ // List of builtin types from Itanium C++ ABI.
++//
++// Invariant: only one- or two-character type abbreviations here.
+ static const AbbrevPair kBuiltinTypeList[] = {
+-  { "v", "void" },
+-  { "w", "wchar_t" },
+-  { "b", "bool" },
+-  { "c", "char" },
+-  { "a", "signed char" },
+-  { "h", "unsigned char" },
+-  { "s", "short" },
+-  { "t", "unsigned short" },
+-  { "i", "int" },
+-  { "j", "unsigned int" },
+-  { "l", "long" },
+-  { "m", "unsigned long" },
+-  { "x", "long long" },
+-  { "y", "unsigned long long" },
+-  { "n", "__int128" },
+-  { "o", "unsigned __int128" },
+-  { "f", "float" },
+-  { "d", "double" },
+-  { "e", "long double" },
+-  { "g", "__float128" },
+-  { "z", "ellipsis" },
+-  { NULL, NULL }
++    {"v", "void", 0},
++    {"w", "wchar_t", 0},
++    {"b", "bool", 0},
++    {"c", "char", 0},
++    {"a", "signed char", 0},
++    {"h", "unsigned char", 0},
++    {"s", "short", 0},
++    {"t", "unsigned short", 0},
++    {"i", "int", 0},
++    {"j", "unsigned int", 0},
++    {"l", "long", 0},
++    {"m", "unsigned long", 0},
++    {"x", "long long", 0},
++    {"y", "unsigned long long", 0},
++    {"n", "__int128", 0},
++    {"o", "unsigned __int128", 0},
++    {"f", "float", 0},
++    {"d", "double", 0},
++    {"e", "long double", 0},
++    {"g", "__float128", 0},
++    {"z", "ellipsis", 0},
++
++    {"De", "decimal128", 0},      // IEEE 754r decimal floating point (128 bits)
++    {"Dd", "decimal64", 0},       // IEEE 754r decimal floating point (64 bits)
++    {"Dc", "decltype(auto)", 0},
++    {"Da", "auto", 0},
++    {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
++    {"Df", "decimal32", 0},       // IEEE 754r decimal floating point (32 bits)
++    {"Di", "char32_t", 0},
++    {"Du", "char8_t", 0},
++    {"Ds", "char16_t", 0},
++    {"Dh", "float16", 0},         // IEEE 754r half-precision float (16 bits)
++    {nullptr, nullptr, 0},
+ };
+ 
+ // List of substitutions Itanium C++ ABI.
+ static const AbbrevPair kSubstitutionList[] = {
+-  { "St", "" },
+-  { "Sa", "allocator" },
+-  { "Sb", "basic_string" },
+-  // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
+-  { "Ss", "string"},
+-  // std::basic_istream<char, std::char_traits<char> >
+-  { "Si", "istream" },
+-  // std::basic_ostream<char, std::char_traits<char> >
+-  { "So", "ostream" },
+-  // std::basic_iostream<char, std::char_traits<char> >
+-  { "Sd", "iostream" },
+-  { NULL, NULL }
++    {"St", "", 0},
++    {"Sa", "allocator", 0},
++    {"Sb", "basic_string", 0},
++    // std::basic_string<char, std::char_traits<char>,std::allocator<char> >
++    {"Ss", "string", 0},
++    // std::basic_istream<char, std::char_traits<char> >
++    {"Si", "istream", 0},
++    // std::basic_ostream<char, std::char_traits<char> >
++    {"So", "ostream", 0},
++    // std::basic_iostream<char, std::char_traits<char> >
++    {"Sd", "iostream", 0},
++    {nullptr, nullptr, 0},
+ };
+ 
+-// State needed for demangling.
++// State needed for demangling.  This struct is copied in almost every stack
++// frame, so every byte counts.
++typedef struct {
++  int mangled_idx;                     // Cursor of mangled name.
++  int out_cur_idx;                     // Cursor of output string.
++  int prev_name_idx;                   // For constructors/destructors.
++  unsigned int prev_name_length : 16;  // For constructors/destructors.
++  signed int nest_level : 15;          // For nested names.
++  unsigned int append : 1;             // Append flag.
++  // Note: for some reason MSVC can't pack "bool append : 1" into the same int
++  // with the above two fields, so we use an int instead.  Amusingly it can pack
++  // "signed bool" as expected, but relying on that to continue to be a legal
++  // type seems ill-advised (as it's illegal in at least clang).
++} ParseState;
++
++static_assert(sizeof(ParseState) == 4 * sizeof(int),
++              "unexpected size of ParseState");
++
++// One-off state for demangling that's not subject to backtracking -- either
++// constant data, data that's intentionally immune to backtracking (steps), or
++// data that would never be changed by backtracking anyway (recursion_depth).
++//
++// Only one copy of this exists for each call to Demangle, so the size of this
++// struct is nearly inconsequential.
+ typedef struct {
+-  const char *mangled_cur;   // Cursor of mangled name.
+-  char *out_cur;             // Cursor of output string.
+-  const char *out_begin;     // Beginning of output string.
+-  const char *out_end;       // End of output string.
+-  const char *prev_name;     // For constructors/destructors.
+-  ssize_t prev_name_length;  // For constructors/destructors.
+-  short nest_level;          // For nested names.
+-  bool append;               // Append flag.
+-  bool overflowed;           // True if output gets overflowed.
++  const char *mangled_begin;  // Beginning of input string.
++  char *out;                  // Beginning of output string.
++  int out_end_idx;            // One past last allowed output character.
++  int recursion_depth;        // For stack exhaustion prevention.
++  int steps;               // Cap how much work we'll do, regardless of depth.
++  ParseState parse_state;  // Backtrackable state copied for most frames.
+ } State;
+ 
++namespace {
++// Prevent deep recursion / stack exhaustion.
++// Also prevent unbounded handling of complex inputs.
++class ComplexityGuard {
++ public:
++  explicit ComplexityGuard(State *state) : state_(state) {
++    ++state->recursion_depth;
++    ++state->steps;
++  }
++  ~ComplexityGuard() { --state_->recursion_depth; }
++
++  // 256 levels of recursion seems like a reasonable upper limit on depth.
++  // 128 is not enough to demagle synthetic tests from demangle_unittest.txt:
++  // "_ZaaZZZZ..." and "_ZaaZcvZcvZ..."
++  static constexpr int kRecursionDepthLimit = 256;
++
++  // We're trying to pick a charitable upper-limit on how many parse steps are
++  // necessary to handle something that a human could actually make use of.
++  // This is mostly in place as a bound on how much work we'll do if we are
++  // asked to demangle an mangled name from an untrusted source, so it should be
++  // much larger than the largest expected symbol, but much smaller than the
++  // amount of work we can do in, e.g., a second.
++  //
++  // Some real-world symbols from an arbitrary binary started failing between
++  // 2^12 and 2^13, so we multiply the latter by an extra factor of 16 to set
++  // the limit.
++  //
++  // Spending one second on 2^17 parse steps would require each step to take
++  // 7.6us, or ~30000 clock cycles, so it's safe to say this can be done in
++  // under a second.
++  static constexpr int kParseStepsLimit = 1 << 17;
++
++  bool IsTooComplex() const {
++    return state_->recursion_depth > kRecursionDepthLimit ||
++           state_->steps > kParseStepsLimit;
++  }
++
++ private:
++  State *state_;
++};
++}  // namespace
++
+ // We don't use strlen() in libc since it's not guaranteed to be async
+ // signal safe.
+ static size_t StrLen(const char *str) {
+@@ -172,8 +255,8 @@ static size_t StrLen(const char *str) {
+ }
+ 
+ // Returns true if "str" has at least "n" characters remaining.
+-static bool AtLeastNumCharsRemaining(const char *str, ssize_t n) {
+-  for (ssize_t i = 0; i < n; ++i) {
++static bool AtLeastNumCharsRemaining(const char *str, size_t n) {
++  for (size_t i = 0; i < n; ++i) {
+     if (str[i] == '\0') {
+       return false;
+     }
+@@ -184,32 +267,42 @@ static bool AtLeastNumCharsRemaining(const char *str, ssize_t n) {
+ // Returns true if "str" has "prefix" as a prefix.
+ static bool StrPrefix(const char *str, const char *prefix) {
+   size_t i = 0;
+-  while (str[i] != '\0' && prefix[i] != '\0' &&
+-         str[i] == prefix[i]) {
++  while (str[i] != '\0' && prefix[i] != '\0' && str[i] == prefix[i]) {
+     ++i;
+   }
+   return prefix[i] == '\0';  // Consumed everything in "prefix".
+ }
+ 
+-static void InitState(State *state, const char *mangled,
+-                      char *out, size_t out_size) {
+-  state->mangled_cur = mangled;
+-  state->out_cur = out;
+-  state->out_begin = out;
+-  state->out_end = out + out_size;
+-  state->prev_name  = NULL;
+-  state->prev_name_length = -1;
+-  state->nest_level = -1;
+-  state->append = true;
+-  state->overflowed = false;
++static void InitState(State* state,
++                      const char* mangled,
++                      char* out,
++                      size_t out_size) {
++  state->mangled_begin = mangled;
++  state->out = out;
++  state->out_end_idx = static_cast<int>(out_size);
++  state->recursion_depth = 0;
++  state->steps = 0;
++
++  state->parse_state.mangled_idx = 0;
++  state->parse_state.out_cur_idx = 0;
++  state->parse_state.prev_name_idx = 0;
++  state->parse_state.prev_name_length = 0;
++  state->parse_state.nest_level = -1;
++  state->parse_state.append = true;
++}
++
++static inline const char *RemainingInput(State *state) {
++  return &state->mangled_begin[state->parse_state.mangled_idx];
+ }
+ 
+-// Returns true and advances "mangled_cur" if we find "one_char_token"
+-// at "mangled_cur" position.  It is assumed that "one_char_token" does
++// Returns true and advances "mangled_idx" if we find "one_char_token"
++// at "mangled_idx" position.  It is assumed that "one_char_token" does
+ // not contain '\0'.
+ static bool ParseOneCharToken(State *state, const char one_char_token) {
+-  if (state->mangled_cur[0] == one_char_token) {
+-    ++state->mangled_cur;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == one_char_token) {
++    ++state->parse_state.mangled_idx;
+     return true;
+   }
+   return false;
+@@ -219,9 +312,11 @@ static bool ParseOneCharToken(State *state, const char one_char_token) {
+ // at "mangled_cur" position.  It is assumed that "two_char_token" does
+ // not contain '\0'.
+ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+-  if (state->mangled_cur[0] == two_char_token[0] &&
+-      state->mangled_cur[1] == two_char_token[1]) {
+-    state->mangled_cur += 2;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == two_char_token[0] &&
++      RemainingInput(state)[1] == two_char_token[1]) {
++    state->parse_state.mangled_idx += 2;
+     return true;
+   }
+   return false;
+@@ -230,21 +325,35 @@ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+ // Returns true and advances "mangled_cur" if we find any character in
+ // "char_class" at "mangled_cur" position.
+ static bool ParseCharClass(State *state, const char *char_class) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (RemainingInput(state)[0] == '\0') {
++    return false;
++  }
+   const char *p = char_class;
+   for (; *p != '\0'; ++p) {
+-    if (state->mangled_cur[0] == *p) {
+-      ++state->mangled_cur;
++    if (RemainingInput(state)[0] == *p) {
++      ++state->parse_state.mangled_idx;
+       return true;
+     }
+   }
+   return false;
+ }
+ 
+-// This function is used for handling an optional non-terminal.
+-static bool Optional(bool) {
+-  return true;
++static bool ParseDigit(State *state, int *digit) {
++  char c = RemainingInput(state)[0];
++  if (ParseCharClass(state, "0123456789")) {
++    if (digit != nullptr) {
++      *digit = c - '0';
++    }
++    return true;
++  }
++  return false;
+ }
+ 
++// This function is used for handling an optional non-terminal.
++static bool Optional(bool /*status*/) { return true; }
++
+ // This function is used for handling <non-terminal>+ syntax.
+ typedef bool (*ParseFunc)(State *);
+ static bool OneOrMore(ParseFunc parse_func, State *state) {
+@@ -266,146 +375,179 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+   return true;
+ }
+ 
+-// Append "str" at "out_cur".  If there is an overflow, "overflowed"
+-// is set to true for later use.  The output string is ensured to
++// Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
++// set to out_end_idx+1.  The output string is ensured to
+ // always terminate with '\0' as long as there is no overflow.
+-static void Append(State *state, const char * const str, ssize_t length) {
+-  for (ssize_t i = 0; i < length; ++i) {
+-    if (state->out_cur + 1 < state->out_end) {  // +1 for '\0'
+-      *state->out_cur = str[i];
+-      ++state->out_cur;
++static void Append(State *state, const char *const str, const size_t length) {
++  for (size_t i = 0; i < length; ++i) {
++    if (state->parse_state.out_cur_idx + 1 <
++        state->out_end_idx) {  // +1 for '\0'
++      state->out[state->parse_state.out_cur_idx++] = str[i];
+     } else {
+-      state->overflowed = true;
++      // signal overflow
++      state->parse_state.out_cur_idx = state->out_end_idx + 1;
+       break;
+     }
+   }
+-  if (!state->overflowed) {
+-    *state->out_cur = '\0';  // Terminate it with '\0'
++  if (state->parse_state.out_cur_idx < state->out_end_idx) {
++    state->out[state->parse_state.out_cur_idx] =
++        '\0';  // Terminate it with '\0'
+   }
+ }
+ 
+ // We don't use equivalents in libc to avoid locale issues.
+-static bool IsLower(char c) {
+-  return c >= 'a' && c <= 'z';
+-}
++static bool IsLower(char c) { return c >= 'a' && c <= 'z'; }
+ 
+ static bool IsAlpha(char c) {
+   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+ 
+-static bool IsDigit(char c) {
+-  return c >= '0' && c <= '9';
+-}
++static bool IsDigit(char c) { return c >= '0' && c <= '9'; }
+ 
+ // Returns true if "str" is a function clone suffix.  These suffixes are used
+-// by GCC 4.5.x and later versions to indicate functions which have been
+-// cloned during optimization.  We treat any sequence (.<alpha>+.<digit>+)+ as
+-// a function clone suffix.
++// by GCC 4.5.x and later versions (and our locally-modified version of GCC
++// 4.4.x) to indicate functions which have been cloned during optimization.
++// We treat any sequence (.<alpha>+.<digit>+)+ as a function clone suffix.
++// Additionally, '_' is allowed along with the alphanumeric sequence.
+ static bool IsFunctionCloneSuffix(const char *str) {
+   size_t i = 0;
+   while (str[i] != '\0') {
+-    // Consume a single .<alpha>+.<digit>+ sequence.
+-    if (str[i] != '.' || !IsAlpha(str[i + 1])) {
+-      return false;
++    bool parsed = false;
++    // Consume a single [.<alpha> | _]*[.<digit>]* sequence.
++    if (str[i] == '.' && (IsAlpha(str[i + 1]) || str[i + 1] == '_')) {
++      parsed = true;
++      i += 2;
++      while (IsAlpha(str[i]) || str[i] == '_') {
++        ++i;
++      }
+     }
+-    i += 2;
+-    while (IsAlpha(str[i])) {
+-      ++i;
++    if (str[i] == '.' && IsDigit(str[i + 1])) {
++      parsed = true;
++      i += 2;
++      while (IsDigit(str[i])) {
++        ++i;
++      }
+     }
+-    if (str[i] != '.' || !IsDigit(str[i + 1])) {
++    if (!parsed)
+       return false;
+-    }
+-    i += 2;
+-    while (IsDigit(str[i])) {
+-      ++i;
+-    }
+   }
+   return true;  // Consumed everything in "str".
+ }
+ 
++static bool EndsWith(State *state, const char chr) {
++  return state->parse_state.out_cur_idx > 0 &&
++         state->parse_state.out_cur_idx < state->out_end_idx &&
++         chr == state->out[state->parse_state.out_cur_idx - 1];
++}
++
+ // Append "str" with some tweaks, iff "append" state is true.
+-// Returns true so that it can be placed in "if" conditions.
+-static void MaybeAppendWithLength(State *state, const char * const str,
+-                                  ssize_t length) {
+-  if (state->append && length > 0) {
++static void MaybeAppendWithLength(State *state, const char *const str,
++                                  const size_t length) {
++  if (state->parse_state.append && length > 0) {
+     // Append a space if the output buffer ends with '<' and "str"
+     // starts with '<' to avoid <<<.
+-    if (str[0] == '<' && state->out_begin < state->out_cur  &&
+-        state->out_cur[-1] == '<') {
++    if (str[0] == '<' && EndsWith(state, '<')) {
+       Append(state, " ", 1);
+     }
+-    // Remember the last identifier name for ctors/dtors.
+-    if (IsAlpha(str[0]) || str[0] == '_') {
+-      state->prev_name = state->out_cur;
+-      state->prev_name_length = length;
++    // Remember the last identifier name for ctors/dtors,
++    // but only if we haven't yet overflown the buffer.
++    if (state->parse_state.out_cur_idx < state->out_end_idx &&
++        (IsAlpha(str[0]) || str[0] == '_')) {
++      state->parse_state.prev_name_idx = state->parse_state.out_cur_idx;
++      state->parse_state.prev_name_length = static_cast<unsigned int>(length);
+     }
+     Append(state, str, length);
+   }
+ }
+ 
+-// A convenient wrapper arount MaybeAppendWithLength().
+-static bool MaybeAppend(State *state, const char * const str) {
+-  if (state->append) {
++// Appends a positive decimal number to the output if appending is enabled.
++static bool MaybeAppendDecimal(State *state, int val) {
++  // Max {32-64}-bit unsigned int is 20 digits.
++  constexpr size_t kMaxLength = 20;
++  char buf[kMaxLength];
++
++  // We can't use itoa or sprintf as neither is specified to be
++  // async-signal-safe.
++  if (state->parse_state.append) {
++    // We can't have a one-before-the-beginning pointer, so instead start with
++    // one-past-the-end and manipulate one character before the pointer.
++    char *p = &buf[kMaxLength];
++    do {  // val=0 is the only input that should write a leading zero digit.
++      *--p = static_cast<char>((val % 10) + '0');
++      val /= 10;
++    } while (p > buf && val != 0);
++
++    // 'p' landed on the last character we set.  How convenient.
++    Append(state, p, kMaxLength - static_cast<size_t>(p - buf));
++  }
++
++  return true;
++}
++
++// A convenient wrapper around MaybeAppendWithLength().
++// Returns true so that it can be placed in "if" conditions.
++static bool MaybeAppend(State *state, const char *const str) {
++  if (state->parse_state.append) {
+     size_t length = StrLen(str);
+-    MaybeAppendWithLength(state, str, static_cast<ssize_t>(length));
++    MaybeAppendWithLength(state, str, length);
+   }
+   return true;
+ }
+ 
+ // This function is used for handling nested names.
+ static bool EnterNestedName(State *state) {
+-  state->nest_level = 0;
++  state->parse_state.nest_level = 0;
+   return true;
+ }
+ 
+ // This function is used for handling nested names.
+-static bool LeaveNestedName(State *state, short prev_value) {
+-  state->nest_level = prev_value;
++static bool LeaveNestedName(State *state, int16_t prev_value) {
++  state->parse_state.nest_level = prev_value;
+   return true;
+ }
+ 
+ // Disable the append mode not to print function parameters, etc.
+ static bool DisableAppend(State *state) {
+-  state->append = false;
++  state->parse_state.append = false;
+   return true;
+ }
+ 
+ // Restore the append mode to the previous state.
+ static bool RestoreAppend(State *state, bool prev_value) {
+-  state->append = prev_value;
++  state->parse_state.append = prev_value;
+   return true;
+ }
+ 
+ // Increase the nest level for nested names.
+ static void MaybeIncreaseNestLevel(State *state) {
+-  if (state->nest_level > -1) {
+-    ++state->nest_level;
++  if (state->parse_state.nest_level > -1) {
++    ++state->parse_state.nest_level;
+   }
+ }
+ 
+ // Appends :: for nested names if necessary.
+ static void MaybeAppendSeparator(State *state) {
+-  if (state->nest_level >= 1) {
++  if (state->parse_state.nest_level >= 1) {
+     MaybeAppend(state, "::");
+   }
+ }
+ 
+ // Cancel the last separator if necessary.
+ static void MaybeCancelLastSeparator(State *state) {
+-  if (state->nest_level >= 1 && state->append &&
+-      state->out_begin <= state->out_cur - 2) {
+-    state->out_cur -= 2;
+-    *state->out_cur = '\0';
++  if (state->parse_state.nest_level >= 1 && state->parse_state.append &&
++      state->parse_state.out_cur_idx >= 2) {
++    state->parse_state.out_cur_idx -= 2;
++    state->out[state->parse_state.out_cur_idx] = '\0';
+   }
+ }
+ 
+ // Returns true if the identifier of the given length pointed to by
+ // "mangled_cur" is anonymous namespace.
+-static bool IdentifierIsAnonymousNamespace(State *state, ssize_t length) {
++static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
++  // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+   static const char anon_prefix[] = "_GLOBAL__N_";
+-  return (length > static_cast<ssize_t>(sizeof(anon_prefix)) -
+-                       1 &&  // Should be longer.
+-          StrPrefix(state->mangled_cur, anon_prefix));
++  return (length > (sizeof(anon_prefix) - 1) &&
++          StrPrefix(RemainingInput(state), anon_prefix));
+ }
+ 
+ // Forward declarations of our parsing functions.
+@@ -413,24 +555,24 @@ static bool ParseMangledName(State *state);
+ static bool ParseEncoding(State *state);
+ static bool ParseName(State *state);
+ static bool ParseUnscopedName(State *state);
+-static bool ParseUnscopedTemplateName(State *state);
+ static bool ParseNestedName(State *state);
+ static bool ParsePrefix(State *state);
+ static bool ParseUnqualifiedName(State *state);
+ static bool ParseSourceName(State *state);
+ static bool ParseLocalSourceName(State *state);
++static bool ParseUnnamedTypeName(State *state);
+ static bool ParseNumber(State *state, int *number_out);
+ static bool ParseFloatNumber(State *state);
+ static bool ParseSeqId(State *state);
+-static bool ParseIdentifier(State *state, ssize_t length);
+-static bool ParseAbiTags(State *state);
+-static bool ParseAbiTag(State *state);
+-static bool ParseOperatorName(State *state);
++static bool ParseIdentifier(State *state, size_t length);
++static bool ParseOperatorName(State *state, int *arity);
+ static bool ParseSpecialName(State *state);
+ static bool ParseCallOffset(State *state);
+ static bool ParseNVOffset(State *state);
+ static bool ParseVOffset(State *state);
++static bool ParseAbiTags(State *state);
+ static bool ParseCtorDtorName(State *state);
++static bool ParseDecltype(State *state);
+ static bool ParseType(State *state);
+ static bool ParseCVQualifiers(State *state);
+ static bool ParseBuiltinType(State *state);
+@@ -443,11 +585,15 @@ static bool ParseTemplateParam(State *state);
+ static bool ParseTemplateTemplateParam(State *state);
+ static bool ParseTemplateArgs(State *state);
+ static bool ParseTemplateArg(State *state);
++static bool ParseBaseUnresolvedName(State *state);
++static bool ParseUnresolvedName(State *state);
+ static bool ParseExpression(State *state);
+ static bool ParseExprPrimary(State *state);
++static bool ParseExprCastValue(State *state);
+ static bool ParseLocalName(State *state);
++static bool ParseLocalNameSuffix(State *state);
+ static bool ParseDiscriminator(State *state);
+-static bool ParseSubstitution(State *state);
++static bool ParseSubstitution(State *state, bool accept_std);
+ 
+ // Implementation note: the following code is a straightforward
+ // translation of the Itanium C++ ABI defined in BNF with a couple of
+@@ -459,11 +605,12 @@ static bool ParseSubstitution(State *state);
+ // - Reorder patterns to give greedier functions precedence
+ //   We'll mark "Less greedy than" for these cases in the code
+ //
+-// Each parsing function changes the state and returns true on
+-// success.  Otherwise, don't change the state and returns false.  To
+-// ensure that the state isn't changed in the latter case, we save the
+-// original state before we call more than one parsing functions
+-// consecutively with &&, and restore the state if unsuccessful.  See
++// Each parsing function changes the parse state and returns true on
++// success, or returns false and doesn't change the parse state (note:
++// the parse-steps counter increases regardless of success or failure).
++// To ensure that the parse state isn't changed in the latter case, we
++// save the original state before we call multiple parsing functions
++// consecutively with &&, and restore it if unsuccessful.  See
+ // ParseEncoding() as an example of this convention.  We follow the
+ // convention throughout the code.
+ //
+@@ -477,10 +624,12 @@ static bool ParseSubstitution(State *state);
+ //
+ // Reference:
+ // - Itanium C++ ABI
+-//   <http://www.codesourcery.com/cxx-abi/abi.html#mangling>
++//   <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling>
+ 
+ // <mangled-name> ::= _Z <encoding>
+ static bool ParseMangledName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
+ }
+ 
+@@ -488,13 +637,18 @@ static bool ParseMangledName(State *state) {
+ //            ::= <(data) name>
+ //            ::= <special-name>
+ static bool ParseEncoding(State *state) {
+-  State copy = *state;
+-  if (ParseName(state) && ParseBareFunctionType(state)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  // Implementing the first two productions together as <name>
++  // [<bare-function-type>] avoids exponential blowup of backtracking.
++  //
++  // Since Optional(...) can't fail, there's no need to copy the state for
++  // backtracking.
++  if (ParseName(state) && Optional(ParseBareFunctionType(state))) {
+     return true;
+   }
+-  *state = copy;
+ 
+-  if (ParseName(state) || ParseSpecialName(state)) {
++  if (ParseSpecialName(state)) {
+     return true;
+   }
+   return false;
+@@ -505,60 +659,73 @@ static bool ParseEncoding(State *state) {
+ //        ::= <unscoped-name>
+ //        ::= <local-name>
+ static bool ParseName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseNestedName(state) || ParseLocalName(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseUnscopedTemplateName(state) &&
++  // We reorganize the productions to avoid re-parsing unscoped names.
++  // - Inline <unscoped-template-name> productions:
++  //   <name> ::= <substitution> <template-args>
++  //          ::= <unscoped-name> <template-args>
++  //          ::= <unscoped-name>
++  // - Merge the two productions that start with unscoped-name:
++  //   <name> ::= <unscoped-name> [<template-args>]
++
++  ParseState copy = state->parse_state;
++  // "std<...>" isn't a valid name.
++  if (ParseSubstitution(state, /*accept_std=*/false) &&
+       ParseTemplateArgs(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  // Less greedy than <unscoped-template-name> <template-args>.
+-  if (ParseUnscopedName(state)) {
+-    return true;
+-  }
+-  return false;
++  // Note there's no need to restore state after this since only the first
++  // subparser can fail.
++  return ParseUnscopedName(state) && Optional(ParseTemplateArgs(state));
+ }
+ 
+ // <unscoped-name> ::= <unqualified-name>
+ //                 ::= St <unqualified-name>
+ static bool ParseUnscopedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseUnqualifiedName(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseTwoCharToken(state, "St") &&
+-      MaybeAppend(state, "std::") &&
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "St") && MaybeAppend(state, "std::") &&
+       ParseUnqualifiedName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <unscoped-template-name> ::= <unscoped-name>
+-//                          ::= <substitution>
+-static bool ParseUnscopedTemplateName(State *state) {
+-  return ParseUnscopedName(state) || ParseSubstitution(state);
++// <ref-qualifer> ::= R // lvalue method reference qualifier
++//                ::= O // rvalue method reference qualifier
++static inline bool ParseRefQualifier(State *state) {
++  return ParseCharClass(state, "OR");
+ }
+ 
+-// <nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E
+-//               ::= N [<CV-qualifiers>] <template-prefix> <template-args> E
++// <nested-name> ::= N [<CV-qualifiers>] [<ref-qualifier>] <prefix>
++//                   <unqualified-name> E
++//               ::= N [<CV-qualifiers>] [<ref-qualifier>] <template-prefix>
++//                   <template-args> E
+ static bool ParseNestedName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'N') &&
+-      EnterNestedName(state) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+       Optional(ParseCVQualifiers(state)) &&
+-      ParsePrefix(state) &&
++      Optional(ParseRefQualifier(state)) && ParsePrefix(state) &&
+       LeaveNestedName(state, copy.nest_level) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+@@ -574,12 +741,15 @@ static bool ParseNestedName(State *state) {
+ //                   ::= <template-param>
+ //                   ::= <substitution>
+ static bool ParsePrefix(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   bool has_something = false;
+   while (true) {
+     MaybeAppendSeparator(state);
+     if (ParseTemplateParam(state) ||
+-        ParseSubstitution(state) ||
+-        ParseUnscopedName(state)) {
++        ParseSubstitution(state, /*accept_std=*/true) ||
++        ParseUnscopedName(state) ||
++        (ParseOneCharToken(state, 'M') && ParseUnnamedTypeName(state))) {
+       has_something = true;
+       MaybeIncreaseNestLevel(state);
+       continue;
+@@ -594,40 +764,112 @@ static bool ParsePrefix(State *state) {
+   return true;
+ }
+ 
+-// <unqualified-name> ::= <operator-name>
+-//                    ::= <ctor-dtor-name>
++// <unqualified-name> ::= <operator-name> [<abi-tags>]
++//                    ::= <ctor-dtor-name> [<abi-tags>]
+ //                    ::= <source-name> [<abi-tags>]
+ //                    ::= <local-source-name> [<abi-tags>]
++//                    ::= <unnamed-type-name> [<abi-tags>]
++//
++// <local-source-name> is a GCC extension; see below.
+ static bool ParseUnqualifiedName(State *state) {
+-  return (ParseOperatorName(state) ||
+-          ParseCtorDtorName(state) ||
+-          (ParseSourceName(state) && Optional(ParseAbiTags(state))) ||
+-          (ParseLocalSourceName(state) && Optional(ParseAbiTags(state))));
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
++      ParseSourceName(state) || ParseLocalSourceName(state) ||
++      ParseUnnamedTypeName(state)) {
++    return ParseAbiTags(state);
++  }
++  return false;
++}
++
++// <abi-tags> ::= <abi-tag> [<abi-tags>]
++// <abi-tag>  ::= B <source-name>
++static bool ParseAbiTags(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  while (ParseOneCharToken(state, 'B')) {
++    ParseState copy = state->parse_state;
++    MaybeAppend(state, "[abi:");
++
++    if (!ParseSourceName(state)) {
++      state->parse_state = copy;
++      return false;
++    }
++    MaybeAppend(state, "]");
++  }
++
++  return true;
+ }
+ 
+ // <source-name> ::= <positive length number> <identifier>
+ static bool ParseSourceName(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   int length = -1;
+-  if (ParseNumber(state, &length) && ParseIdentifier(state, length)) {
++  if (ParseNumber(state, &length) &&
++      ParseIdentifier(state, static_cast<size_t>(length))) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <local-source-name> ::= L <source-name> [<discriminator>]
+ //
+ // References:
+-//   http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
+-//   http://gcc.gnu.org/viewcvs?view=rev&revision=124467
++//   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31775
++//   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
+ static bool ParseLocalSourceName(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
+       Optional(ParseDiscriminator(state))) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++  return false;
++}
++
++// <unnamed-type-name> ::= Ut [<(nonnegative) number>] _
++//                     ::= <closure-type-name>
++// <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
++// <lambda-sig>        ::= <(parameter) type>+
++static bool ParseUnnamedTypeName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
++  // Optionally parse the encoded value into 'which' and add 2 to get the index.
++  int which = -1;
++
++  // Unnamed type local to function or class.
++  if (ParseTwoCharToken(state, "Ut") && Optional(ParseNumber(state, &which)) &&
++      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
++      ParseOneCharToken(state, '_')) {
++    MaybeAppend(state, "{unnamed type#");
++    MaybeAppendDecimal(state, 2 + which);
++    MaybeAppend(state, "}");
++    return true;
++  }
++  state->parse_state = copy;
++
++  // Closure type.
++  which = -1;
++  if (ParseTwoCharToken(state, "Ul") && DisableAppend(state) &&
++      OneOrMore(ParseType, state) && RestoreAppend(state, copy.append) &&
++      ParseOneCharToken(state, 'E') && Optional(ParseNumber(state, &which)) &&
++      which <= std::numeric_limits<int>::max() - 2 &&  // Don't overflow.
++      ParseOneCharToken(state, '_')) {
++    MaybeAppend(state, "{lambda()#");
++    MaybeAppendDecimal(state, 2 + which);
++    MaybeAppend(state, "}");
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -635,23 +877,32 @@ static bool ParseLocalSourceName(State *state) {
+ // If "number_out" is non-null, then *number_out is set to the value of the
+ // parsed number on success.
+ static bool ParseNumber(State *state, int *number_out) {
+-  int sign = 1;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  bool negative = false;
+   if (ParseOneCharToken(state, 'n')) {
+-    sign = -1;
++    negative = true;
+   }
+-  const char *p = state->mangled_cur;
+-  int number = 0;
+-  for (;*p != '\0'; ++p) {
++  const char *p = RemainingInput(state);
++  uint64_t number = 0;
++  for (; *p != '\0'; ++p) {
+     if (IsDigit(*p)) {
+-      number = number * 10 + (*p - '0');
++      number = number * 10 + static_cast<uint64_t>(*p - '0');
+     } else {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
+-    if (number_out != NULL) {
+-      *number_out = number * sign;
++  // Apply the sign with uint64_t arithmetic so overflows aren't UB.  Gives
++  // "incorrect" results for out-of-range inputs, but negative values only
++  // appear for literals, which aren't printed.
++  if (negative) {
++    number = ~number + 1;
++  }
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
++    if (number_out != nullptr) {
++      // Note: possibly truncate "number".
++      *number_out = static_cast<int>(number);
+     }
+     return true;
+   }
+@@ -661,14 +912,16 @@ static bool ParseNumber(State *state, int *number_out) {
+ // Floating-point literals are encoded using a fixed-length lowercase
+ // hexadecimal string.
+ static bool ParseFloatNumber(State *state) {
+-  const char *p = state->mangled_cur;
+-  for (;*p != '\0'; ++p) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  const char *p = RemainingInput(state);
++  for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
+     return true;
+   }
+   return false;
+@@ -677,93 +930,85 @@ static bool ParseFloatNumber(State *state) {
+ // The <seq-id> is a sequence number in base 36,
+ // using digits and upper case letters
+ static bool ParseSeqId(State *state) {
+-  const char *p = state->mangled_cur;
+-  for (;*p != '\0'; ++p) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  const char *p = RemainingInput(state);
++  for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
+       break;
+     }
+   }
+-  if (p != state->mangled_cur) {  // Conversion succeeded.
+-    state->mangled_cur = p;
++  if (p != RemainingInput(state)) {  // Conversion succeeded.
++    state->parse_state.mangled_idx += p - RemainingInput(state);
+     return true;
+   }
+   return false;
+ }
+ 
+ // <identifier> ::= <unqualified source code identifier> (of given length)
+-static bool ParseIdentifier(State *state, ssize_t length) {
+-  if (length == -1 ||
+-      !AtLeastNumCharsRemaining(state->mangled_cur, length)) {
++static bool ParseIdentifier(State *state, size_t length) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+     return false;
+   }
+   if (IdentifierIsAnonymousNamespace(state, length)) {
+     MaybeAppend(state, "(anonymous namespace)");
+   } else {
+-    MaybeAppendWithLength(state, state->mangled_cur, length);
++    MaybeAppendWithLength(state, RemainingInput(state), length);
+   }
+-  state->mangled_cur += length;
++  state->parse_state.mangled_idx += length;
+   return true;
+ }
+ 
+-// <abi-tags> ::= <abi-tag> [<abi-tags>]
+-static bool ParseAbiTags(State *state) {
+-  State copy = *state;
+-  DisableAppend(state);
+-  if (OneOrMore(ParseAbiTag, state)) {
+-    RestoreAppend(state, copy.append);
+-    return true;
+-  }
+-  *state = copy;
+-  return false;
+-}
+-
+-// <abi-tag> ::= B <source-name>
+-static bool ParseAbiTag(State *state) {
+-  return ParseOneCharToken(state, 'B') && ParseSourceName(state);
+-}
+-
+ // <operator-name> ::= nw, and other two letters cases
+ //                 ::= cv <type>  # (cast)
+ //                 ::= v  <digit> <source-name> # vendor extended operator
+-static bool ParseOperatorName(State *state) {
+-  if (!AtLeastNumCharsRemaining(state->mangled_cur, 2)) {
++static bool ParseOperatorName(State *state, int *arity) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
+     return false;
+   }
+   // First check with "cv" (cast) case.
+-  State copy = *state;
+-  if (ParseTwoCharToken(state, "cv") &&
+-      MaybeAppend(state, "operator ") &&
+-      EnterNestedName(state) &&
+-      ParseType(state) &&
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "cv") && MaybeAppend(state, "operator ") &&
++      EnterNestedName(state) && ParseType(state) &&
+       LeaveNestedName(state, copy.nest_level)) {
++    if (arity != nullptr) {
++      *arity = 1;
++    }
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Then vendor extended operators.
+-  if (ParseOneCharToken(state, 'v') && ParseCharClass(state, "0123456789") &&
++  if (ParseOneCharToken(state, 'v') && ParseDigit(state, arity) &&
+       ParseSourceName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Other operator names should start with a lower alphabet followed
+   // by a lower/upper alphabet.
+-  if (!(IsLower(state->mangled_cur[0]) &&
+-        IsAlpha(state->mangled_cur[1]))) {
++  if (!(IsLower(RemainingInput(state)[0]) &&
++        IsAlpha(RemainingInput(state)[1]))) {
+     return false;
+   }
+   // We may want to perform a binary search if we really need speed.
+   const AbbrevPair *p;
+-  for (p = kOperatorList; p->abbrev != NULL; ++p) {
+-    if (state->mangled_cur[0] == p->abbrev[0] &&
+-        state->mangled_cur[1] == p->abbrev[1]) {
++  for (p = kOperatorList; p->abbrev != nullptr; ++p) {
++    if (RemainingInput(state)[0] == p->abbrev[0] &&
++        RemainingInput(state)[1] == p->abbrev[1]) {
++      if (arity != nullptr) {
++        *arity = p->arity;
++      }
+       MaybeAppend(state, "operator");
+       if (IsLower(*p->real_name)) {  // new, delete, etc.
+         MaybeAppend(state, " ");
+       }
+       MaybeAppend(state, p->real_name);
+-      state->mangled_cur += 2;
++      state->parse_state.mangled_idx += 2;
+       return true;
+     }
+   }
+@@ -774,6 +1019,7 @@ static bool ParseOperatorName(State *state) {
+ //                ::= TT <type>
+ //                ::= TI <type>
+ //                ::= TS <type>
++//                ::= TH <type>  # thread-local
+ //                ::= Tc <call-offset> <call-offset> <(base) encoding>
+ //                ::= GV <(object) name>
+ //                ::= T <call-offset> <(base) encoding>
+@@ -789,123 +1035,156 @@ static bool ParseOperatorName(State *state) {
+ // Note: we don't care much about them since they don't appear in
+ // stack traces.  The are special data.
+ static bool ParseSpecialName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'T') &&
+-      ParseCharClass(state, "VTIS") &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "Tc") && ParseCallOffset(state) &&
+       ParseCallOffset(state) && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "GV") &&
+-      ParseName(state)) {
++  if (ParseTwoCharToken(state, "GV") && ParseName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCallOffset(state) &&
+       ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // G++ extensions
+   if (ParseTwoCharToken(state, "TC") && ParseType(state) &&
+-      ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
+-      DisableAppend(state) &&
+-      ParseType(state)) {
++      ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
++      DisableAppend(state) && ParseType(state)) {
+     RestoreAppend(state, copy.append);
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "FJ") &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "GR") && ParseName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "GA") && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "hv") &&
+       ParseCallOffset(state) && ParseEncoding(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <call-offset> ::= h <nv-offset> _
+ //               ::= v <v-offset> _
+ static bool ParseCallOffset(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'h') &&
+-      ParseNVOffset(state) && ParseOneCharToken(state, '_')) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
++      ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOneCharToken(state, 'v') &&
+-      ParseVOffset(state) && ParseOneCharToken(state, '_')) {
++  if (ParseOneCharToken(state, 'v') && ParseVOffset(state) &&
++      ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   return false;
+ }
+ 
+ // <nv-offset> ::= <(offset) number>
+ static bool ParseNVOffset(State *state) {
+-  return ParseNumber(state, NULL);
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  return ParseNumber(state, nullptr);
+ }
+ 
+ // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
+ static bool ParseVOffset(State *state) {
+-  State copy = *state;
+-  if (ParseNumber(state, NULL) && ParseOneCharToken(state, '_') &&
+-      ParseNumber(state, NULL)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
++      ParseNumber(state, nullptr)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <ctor-dtor-name> ::= C1 | C2 | C3
++// <ctor-dtor-name> ::= C1 | C2 | C3 | CI1 <base-class-type> | CI2
++// <base-class-type>
+ //                  ::= D0 | D1 | D2
++// # GCC extensions: "unified" constructor/destructor.  See
++// #
++// https://github.com/gcc-mirror/gcc/blob/7ad17b583c3643bd4557f29b8391ca7ef08391f5/gcc/cp/mangle.c#L1847
++//                  ::= C4 | D4
+ static bool ParseCtorDtorName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'C') &&
+-      ParseCharClass(state, "123")) {
+-    const char * const prev_name = state->prev_name;
+-    const ssize_t prev_name_length = state->prev_name_length;
+-    MaybeAppendWithLength(state, prev_name, prev_name_length);
+-    return true;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'C')) {
++    if (ParseCharClass(state, "1234")) {
++      const char *const prev_name =
++          state->out + state->parse_state.prev_name_idx;
++      MaybeAppendWithLength(state, prev_name,
++                            state->parse_state.prev_name_length);
++      return true;
++    } else if (ParseOneCharToken(state, 'I') && ParseCharClass(state, "12") &&
++               ParseClassEnumType(state)) {
++      return true;
++    }
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOneCharToken(state, 'D') &&
+-      ParseCharClass(state, "012")) {
+-    const char * const prev_name = state->prev_name;
+-    const ssize_t prev_name_length = state->prev_name_length;
++  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
++    const char *const prev_name = state->out + state->parse_state.prev_name_idx;
+     MaybeAppend(state, "~");
+-    MaybeAppendWithLength(state, prev_name, prev_name_length);
++    MaybeAppendWithLength(state, prev_name,
++                          state->parse_state.prev_name_length);
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++  return false;
++}
++
++// <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
++//                                   # member access (C++0x)
++//            ::= DT <expression> E  # decltype of an expression (C++0x)
++static bool ParseDecltype(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
++      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -918,67 +1197,87 @@ static bool ParseCtorDtorName(State *state) {
+ //        ::= U <source-name> <type>  # vendor extended type qualifier
+ //        ::= <builtin-type>
+ //        ::= <function-type>
+-//        ::= <class-enum-type>
++//        ::= <class-enum-type>  # note: just an alias for <name>
+ //        ::= <array-type>
+ //        ::= <pointer-to-member-type>
+ //        ::= <template-template-param> <template-args>
+ //        ::= <template-param>
++//        ::= <decltype>
+ //        ::= <substitution>
+ //        ::= Dp <type>          # pack expansion of (C++0x)
+-//        ::= Dt <expression> E  # decltype of an id-expression or class
+-//                               # member access (C++0x)
+-//        ::= DT <expression> E  # decltype of an expression (C++0x)
++//        ::= Dv <num-elems> _   # GNU vector extension
+ //
+ static bool ParseType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++
+   // We should check CV-qualifers, and PRGC things first.
+-  State copy = *state;
+-  if (ParseCVQualifiers(state) && ParseType(state)) {
+-    return true;
++  //
++  // CV-qualifiers overlap with some operator names, but an operator name is not
++  // valid as a type.  To avoid an ambiguity that can lead to exponential time
++  // complexity, refuse to backtrack the CV-qualifiers.
++  //
++  // _Z4aoeuIrMvvE
++  //  => _Z 4aoeuI        rM  v     v   E
++  //         aoeu<operator%=, void, void>
++  //  => _Z 4aoeuI r Mv v              E
++  //         aoeu<void void::* restrict>
++  //
++  // By consuming the CV-qualifiers first, the former parse is disabled.
++  if (ParseCVQualifiers(state)) {
++    const bool result = ParseType(state);
++    if (!result) state->parse_state = copy;
++    return result;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseCharClass(state, "OPRCG") && ParseType(state)) {
+-    return true;
++  // Similarly, these tag characters can overlap with other <name>s resulting in
++  // two different parse prefixes that land on <template-args> in the same
++  // place, such as "C3r1xI...".  So, disable the "ctor-name = C3" parse by
++  // refusing to backtrack the tag characters.
++  if (ParseCharClass(state, "OPRCG")) {
++    const bool result = ParseType(state);
++    if (!result) state->parse_state = copy;
++    return result;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseTwoCharToken(state, "Dp") && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
+-
+-  if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+-      ParseExpression(state) && ParseOneCharToken(state, 'E')) {
+-    return true;
+-  }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'U') && ParseSourceName(state) &&
+       ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseBuiltinType(state) ||
+-      ParseFunctionType(state) ||
+-      ParseClassEnumType(state) ||
+-      ParseArrayType(state) ||
+-      ParsePointerToMemberType(state) ||
+-      ParseSubstitution(state)) {
++  if (ParseBuiltinType(state) || ParseFunctionType(state) ||
++      ParseClassEnumType(state) || ParseArrayType(state) ||
++      ParsePointerToMemberType(state) || ParseDecltype(state) ||
++      // "std" on its own isn't a type.
++      ParseSubstitution(state, /*accept_std=*/false)) {
+     return true;
+   }
+ 
+-  if (ParseTemplateTemplateParam(state) &&
+-      ParseTemplateArgs(state)) {
++  if (ParseTemplateTemplateParam(state) && ParseTemplateArgs(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Less greedy than <template-template-param> <template-args>.
+   if (ParseTemplateParam(state)) {
+     return true;
+   }
+ 
++  if (ParseTwoCharToken(state, "Dv") && ParseNumber(state, nullptr) &&
++      ParseOneCharToken(state, '_')) {
++    return true;
++  }
++  state->parse_state = copy;
++
+   return false;
+ }
+ 
+@@ -986,6 +1285,8 @@ static bool ParseType(State *state) {
+ // We don't allow empty <CV-qualifiers> to avoid infinite loop in
+ // ParseType().
+ static bool ParseCVQualifiers(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   int num_cv_qualifiers = 0;
+   num_cv_qualifiers += ParseOneCharToken(state, 'r');
+   num_cv_qualifiers += ParseOneCharToken(state, 'V');
+@@ -993,208 +1294,499 @@ static bool ParseCVQualifiers(State *state) {
+   return num_cv_qualifiers > 0;
+ }
+ 
+-// <builtin-type> ::= v, etc.
++// <builtin-type> ::= v, etc.  # single-character builtin types
+ //                ::= u <source-name>
++//                ::= Dd, etc.  # two-character builtin types
++//
++// Not supported:
++//                ::= DF <number> _ # _FloatN (N bits)
++//
+ static bool ParseBuiltinType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   const AbbrevPair *p;
+-  for (p = kBuiltinTypeList; p->abbrev != NULL; ++p) {
+-    if (state->mangled_cur[0] == p->abbrev[0]) {
++  for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
++    // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
++    if (p->abbrev[1] == '\0') {
++      if (ParseOneCharToken(state, p->abbrev[0])) {
++        MaybeAppend(state, p->real_name);
++        return true;
++      }
++    } else if (p->abbrev[2] == '\0' && ParseTwoCharToken(state, p->abbrev)) {
+       MaybeAppend(state, p->real_name);
+-      ++state->mangled_cur;
+       return true;
+     }
+   }
+ 
+-  State copy = *state;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'u') && ParseSourceName(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <function-type> ::= F [Y] <bare-function-type> E
++//  <exception-spec> ::= Do                # non-throwing
++//                                           exception-specification (e.g.,
++//                                           noexcept, throw())
++//                   ::= DO <expression> E # computed (instantiation-dependent)
++//                                           noexcept
++//                   ::= Dw <type>+ E      # dynamic exception specification
++//                                           with instantiation-dependent types
++static bool ParseExceptionSpec(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (ParseTwoCharToken(state, "Do")) return true;
++
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
++      ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++  if (ParseTwoCharToken(state, "Dw") && OneOrMore(ParseType, state) &&
++      ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+ static bool ParseFunctionType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'F') &&
+-      Optional(ParseOneCharToken(state, 'Y')) &&
+-      ParseBareFunctionType(state) && ParseOneCharToken(state, 'E')) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
++      Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
++      Optional(ParseOneCharToken(state, 'O')) &&
++      ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <bare-function-type> ::= <(signature) type>+
+ static bool ParseBareFunctionType(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (OneOrMore(ParseType, state)) {
+     RestoreAppend(state, copy.append);
+     MaybeAppend(state, "()");
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <class-enum-type> ::= <name>
+ static bool ParseClassEnumType(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return ParseName(state);
+ }
+ 
+ // <array-type> ::= A <(positive dimension) number> _ <(element) type>
+ //              ::= A [<(dimension) expression>] _ <(element) type>
+ static bool ParseArrayType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'A') && ParseNumber(state, NULL) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'A') && Optional(ParseExpression(state)) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <pointer-to-member-type> ::= M <(class) type> <(member) type>
+ static bool ParsePointerToMemberType(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'M') && ParseType(state) &&
+-      ParseType(state)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <template-param> ::= T_
+ //                  ::= T <parameter-2 non-negative number> _
+ static bool ParseTemplateParam(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTwoCharToken(state, "T_")) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'T') && ParseNumber(state, NULL) &&
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'T') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_')) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-
+ // <template-template-param> ::= <template-param>
+ //                           ::= <substitution>
+ static bool ParseTemplateTemplateParam(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   return (ParseTemplateParam(state) ||
+-          ParseSubstitution(state));
++          // "std" on its own isn't a template.
++          ParseSubstitution(state, /*accept_std=*/false));
+ }
+ 
+ // <template-args> ::= I <template-arg>+ E
+ static bool ParseTemplateArgs(State *state) {
+-  State copy = *state;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   DisableAppend(state);
+-  if (ParseOneCharToken(state, 'I') &&
+-      OneOrMore(ParseTemplateArg, state) &&
++  if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+     RestoreAppend(state, copy.append);
+     MaybeAppend(state, "<>");
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <template-arg>  ::= <type>
+ //                 ::= <expr-primary>
+-//                 ::= I <template-arg>* E        # argument pack
+ //                 ::= J <template-arg>* E        # argument pack
+ //                 ::= X <expression> E
+ static bool ParseTemplateArg(State *state) {
+-  State copy = *state;
+-  if ((ParseOneCharToken(state, 'I') || ParseOneCharToken(state, 'J')) &&
+-      ZeroOrMore(ParseTemplateArg, state) &&
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++
++  // There can be significant overlap between the following leading to
++  // exponential backtracking:
++  //
++  //   <expr-primary> ::= L <type> <expr-cast-value> E
++  //                 e.g. L 2xxIvE 1                 E
++  //   <type>         ==> <local-source-name> <template-args>
++  //                 e.g. L 2xx               IvE
++  //
++  // This means parsing an entire <type> twice, and <type> can contain
++  // <template-arg>, so this can generate exponential backtracking.  There is
++  // only overlap when the remaining input starts with "L <source-name>", so
++  // parse all cases that can start this way jointly to share the common prefix.
++  //
++  // We have:
++  //
++  //   <template-arg> ::= <type>
++  //                  ::= <expr-primary>
++  //
++  // First, drop all the productions of <type> that must start with something
++  // other than 'L'.  All that's left is <class-enum-type>; inline it.
++  //
++  //   <type> ::= <nested-name> # starts with 'N'
++  //          ::= <unscoped-name>
++  //          ::= <unscoped-template-name> <template-args>
++  //          ::= <local-name> # starts with 'Z'
++  //
++  // Drop and inline again:
++  //
++  //   <type> ::= <unscoped-name>
++  //          ::= <unscoped-name> <template-args>
++  //          ::= <substitution> <template-args> # starts with 'S'
++  //
++  // Merge the first two, inline <unscoped-name>, drop last:
++  //
++  //   <type> ::= <unqualified-name> [<template-args>]
++  //          ::= St <unqualified-name> [<template-args>] # starts with 'S'
++  //
++  // Drop and inline:
++  //
++  //   <type> ::= <operator-name> [<template-args>] # starts with lowercase
++  //          ::= <ctor-dtor-name> [<template-args>] # starts with 'C' or 'D'
++  //          ::= <source-name> [<template-args>] # starts with digit
++  //          ::= <local-source-name> [<template-args>]
++  //          ::= <unnamed-type-name> [<template-args>] # starts with 'U'
++  //
++  // One more time:
++  //
++  //   <type> ::= L <source-name> [<template-args>]
++  //
++  // Likewise with <expr-primary>:
++  //
++  //   <expr-primary> ::= L <type> <expr-cast-value> E
++  //                  ::= LZ <encoding> E # cannot overlap; drop
++  //                  ::= L <mangled_name> E # cannot overlap; drop
++  //
++  // By similar reasoning as shown above, the only <type>s starting with
++  // <source-name> are "<source-name> [<template-args>]".  Inline this.
++  //
++  //   <expr-primary> ::= L <source-name> [<template-args>] <expr-cast-value> E
++  //
++  // Now inline both of these into <template-arg>:
++  //
++  //   <template-arg> ::= L <source-name> [<template-args>]
++  //                  ::= L <source-name> [<template-args>] <expr-cast-value> E
++  //
++  // Merge them and we're done:
++  //   <template-arg>
++  //     ::= L <source-name> [<template-args>] [<expr-cast-value> E]
++  if (ParseLocalSourceName(state) && Optional(ParseTemplateArgs(state))) {
++    copy = state->parse_state;
++    if (ParseExprCastValue(state) && ParseOneCharToken(state, 'E')) {
++      return true;
++    }
++    state->parse_state = copy;
++    return true;
++  }
+ 
+-  if (ParseType(state) ||
+-      ParseExprPrimary(state)) {
++  // Now that the overlapping cases can't reach this code, we can safely call
++  // both of these.
++  if (ParseType(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'X') && ParseExpression(state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+-// <expression> ::= <template-param>
+-//              ::= <expr-primary>
+-//              ::= <unary operator-name> <expression>
+-//              ::= <binary operator-name> <expression> <expression>
+-//              ::= <trinary operator-name> <expression> <expression>
+-//                  <expression>
++// <unresolved-type> ::= <template-param> [<template-args>]
++//                   ::= <decltype>
++//                   ::= <substitution>
++static inline bool ParseUnresolvedType(State *state) {
++  // No ComplexityGuard because we don't copy the state in this stack frame.
++  return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
++         ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
++}
++
++// <simple-id> ::= <source-name> [<template-args>]
++static inline bool ParseSimpleId(State *state) {
++  // No ComplexityGuard because we don't copy the state in this stack frame.
++
++  // Note: <simple-id> cannot be followed by a parameter pack; see comment in
++  // ParseUnresolvedType.
++  return ParseSourceName(state) && Optional(ParseTemplateArgs(state));
++}
++
++// <base-unresolved-name> ::= <source-name> [<template-args>]
++//                        ::= on <operator-name> [<template-args>]
++//                        ::= dn <destructor-name>
++static bool ParseBaseUnresolvedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (ParseSimpleId(state)) {
++    return true;
++  }
++
++  ParseState copy = state->parse_state;
++  if (ParseTwoCharToken(state, "on") && ParseOperatorName(state, nullptr) &&
++      Optional(ParseTemplateArgs(state))) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "dn") &&
++      (ParseUnresolvedType(state) || ParseSimpleId(state))) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <unresolved-name> ::= [gs] <base-unresolved-name>
++//                   ::= sr <unresolved-type> <base-unresolved-name>
++//                   ::= srN <unresolved-type> <unresolved-qualifier-level>+ E
++//                         <base-unresolved-name>
++//                   ::= [gs] sr <unresolved-qualifier-level>+ E
++//                         <base-unresolved-name>
++static bool ParseUnresolvedName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  ParseState copy = state->parse_state;
++  if (Optional(ParseTwoCharToken(state, "gs")) &&
++      ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "sr") && ParseUnresolvedType(state) &&
++      ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (ParseTwoCharToken(state, "sr") && ParseOneCharToken(state, 'N') &&
++      ParseUnresolvedType(state) &&
++      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
++      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  if (Optional(ParseTwoCharToken(state, "gs")) &&
++      ParseTwoCharToken(state, "sr") &&
++      OneOrMore(/* <unresolved-qualifier-level> ::= */ ParseSimpleId, state) &&
++      ParseOneCharToken(state, 'E') && ParseBaseUnresolvedName(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return false;
++}
++
++// <expression> ::= <1-ary operator-name> <expression>
++//              ::= <2-ary operator-name> <expression> <expression>
++//              ::= <3-ary operator-name> <expression> <expression> <expression>
++//              ::= cl <expression>+ E
++//              ::= cp <simple-id> <expression>* E # Clang-specific.
++//              ::= cv <type> <expression>      # type (expression)
++//              ::= cv <type> _ <expression>* E # type (expr-list)
+ //              ::= st <type>
++//              ::= <template-param>
++//              ::= <function-param>
++//              ::= <expr-primary>
++//              ::= dt <expression> <unresolved-name> # expr.name
++//              ::= pt <expression> <unresolved-name> # expr->name
++//              ::= sp <expression>         # argument pack expansion
+ //              ::= sr <type> <unqualified-name> <template-args>
+ //              ::= sr <type> <unqualified-name>
++// <function-param> ::= fp <(top-level) CV-qualifiers> _
++//                  ::= fp <(top-level) CV-qualifiers> <number> _
++//                  ::= fL <number> p <(top-level) CV-qualifiers> _
++//                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
+ static bool ParseExpression(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+ 
+-  State copy = *state;
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state)) {
++  ParseState copy = state->parse_state;
++
++  // Object/function call expression.
++  if (ParseTwoCharToken(state, "cl") && OneOrMore(ParseExpression, state) &&
++      ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state) &&
+-      ParseExpression(state)) {
++  // Clang-specific "cp <simple-id> <expression>* E"
++  //   https://clang.llvm.org/doxygen/ItaniumMangle_8cpp_source.html#l04338
++  if (ParseTwoCharToken(state, "cp") && ParseSimpleId(state) &&
++      ZeroOrMore(ParseExpression, state) && ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseOperatorName(state) &&
+-      ParseExpression(state)) {
++  // Function-param expression (level 0).
++  if (ParseTwoCharToken(state, "fp") && Optional(ParseCVQualifiers(state)) &&
++      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
++  // Function-param expression (level 1+).
++  if (ParseTwoCharToken(state, "fL") && Optional(ParseNumber(state, nullptr)) &&
++      ParseOneCharToken(state, 'p') && Optional(ParseCVQualifiers(state)) &&
++      Optional(ParseNumber(state, nullptr)) && ParseOneCharToken(state, '_')) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  // Parse the conversion expressions jointly to avoid re-parsing the <type> in
++  // their common prefix.  Parsed as:
++  // <expression> ::= cv <type> <conversion-args>
++  // <conversion-args> ::= _ <expression>* E
++  //                   ::= <expression>
++  //
++  // Also don't try ParseOperatorName after seeing "cv", since ParseOperatorName
++  // also needs to accept "cv <type>" in other contexts.
++  if (ParseTwoCharToken(state, "cv")) {
++    if (ParseType(state)) {
++      ParseState copy2 = state->parse_state;
++      if (ParseOneCharToken(state, '_') && ZeroOrMore(ParseExpression, state) &&
++          ParseOneCharToken(state, 'E')) {
++        return true;
++      }
++      state->parse_state = copy2;
++      if (ParseExpression(state)) {
++        return true;
++      }
++    }
++  } else {
++    // Parse unary, binary, and ternary operator expressions jointly, taking
++    // care not to re-parse subexpressions repeatedly. Parse like:
++    //   <expression> ::= <operator-name> <expression>
++    //                    [<one-to-two-expressions>]
++    //   <one-to-two-expressions> ::= <expression> [<expression>]
++    int arity = -1;
++    if (ParseOperatorName(state, &arity) &&
++        arity > 0 &&  // 0 arity => disabled.
++        (arity < 3 || ParseExpression(state)) &&
++        (arity < 2 || ParseExpression(state)) &&
++        (arity < 1 || ParseExpression(state))) {
++      return true;
++    }
++  }
++  state->parse_state = copy;
++
++  // sizeof type
+   if (ParseTwoCharToken(state, "st") && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
+-      ParseUnqualifiedName(state) &&
+-      ParseTemplateArgs(state)) {
++  // Object and pointer member access expressions.
++  if ((ParseTwoCharToken(state, "dt") || ParseTwoCharToken(state, "pt")) &&
++      ParseExpression(state) && ParseType(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "sr") && ParseType(state) &&
+-      ParseUnqualifiedName(state)) {
++  // Pointer-to-member access expressions.  This parses the same as a binary
++  // operator, but it's implemented separately because "ds" shouldn't be
++  // accepted in other contexts that parse an operator name.
++  if (ParseTwoCharToken(state, "ds") && ParseExpression(state) &&
++      ParseExpression(state)) {
+     return true;
+   }
+-  *state = copy;
+-  return false;
++  state->parse_state = copy;
++
++  // Parameter pack expansion
++  if (ParseTwoCharToken(state, "sp") && ParseExpression(state)) {
++    return true;
++  }
++  state->parse_state = copy;
++
++  return ParseUnresolvedName(state);
+ }
+ 
+ // <expr-primary> ::= L <type> <(value) number> E
+@@ -1202,116 +1794,194 @@ static bool ParseExpression(State *state) {
+ //                ::= L <mangled-name> E
+ //                // A bug in g++'s C++ ABI version 2 (-fabi-version=2).
+ //                ::= LZ <encoding> E
++//
++// Warning, subtle: the "bug" LZ production above is ambiguous with the first
++// production where <type> starts with <local-name>, which can lead to
++// exponential backtracking in two scenarios:
++//
++// - When whatever follows the E in the <local-name> in the first production is
++//   not a name, we backtrack the whole <encoding> and re-parse the whole thing.
++//
++// - When whatever follows the <local-name> in the first production is not a
++//   number and this <expr-primary> may be followed by a name, we backtrack the
++//   <name> and re-parse it.
++//
++// Moreover this ambiguity isn't always resolved -- for example, the following
++// has two different parses:
++//
++//   _ZaaILZ4aoeuE1x1EvE
++//   => operator&&<aoeu, x, E, void>
++//   => operator&&<(aoeu::x)(1), void>
++//
++// To resolve this, we just do what GCC's demangler does, and refuse to parse
++// casts to <local-name> types.
+ static bool ParseExprPrimary(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+-      ParseNumber(state, NULL) &&
+-      ParseOneCharToken(state, 'E')) {
+-    return true;
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++
++  // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
++  // or fail, no backtracking.
++  if (ParseTwoCharToken(state, "LZ")) {
++    if (ParseEncoding(state) && ParseOneCharToken(state, 'E')) {
++      return true;
++    }
++
++    state->parse_state = copy;
++    return false;
+   }
+-  *state = copy;
+ 
++  // The merged cast production.
+   if (ParseOneCharToken(state, 'L') && ParseType(state) &&
+-      ParseFloatNumber(state) &&
+-      ParseOneCharToken(state, 'E')) {
++      ParseExprCastValue(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'L') && ParseMangledName(state) &&
+       ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+-  if (ParseTwoCharToken(state, "LZ") && ParseEncoding(state) &&
+-      ParseOneCharToken(state, 'E')) {
++  return false;
++}
++
++// <number> or <float>, followed by 'E', as described above ParseExprPrimary.
++static bool ParseExprCastValue(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  // We have to be able to backtrack after accepting a number because we could
++  // have e.g. "7fffE", which will accept "7" as a number but then fail to find
++  // the 'E'.
++  ParseState copy = state->parse_state;
++  if (ParseNumber(state, nullptr) && ParseOneCharToken(state, 'E')) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
++
++  if (ParseFloatNumber(state) && ParseOneCharToken(state, 'E')) {
++    return true;
++  }
++  state->parse_state = copy;
+ 
+   return false;
+ }
+ 
+-// <local-name> := Z <(function) encoding> E <(entity) name>
+-//                 [<discriminator>]
+-//              := Z <(function) encoding> E s [<discriminator>]
+-static bool ParseLocalName(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+-      ParseOneCharToken(state, 'E') && MaybeAppend(state, "::") &&
+-      ParseName(state) && Optional(ParseDiscriminator(state))) {
++// <local-name> ::= Z <(function) encoding> E <(entity) name> [<discriminator>]
++//              ::= Z <(function) encoding> E s [<discriminator>]
++//
++// Parsing a common prefix of these two productions together avoids an
++// exponential blowup of backtracking.  Parse like:
++//   <local-name> := Z <encoding> E <local-name-suffix>
++//   <local-name-suffix> ::= s [<discriminator>]
++//                       ::= <name> [<discriminator>]
++
++static bool ParseLocalNameSuffix(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++
++  if (MaybeAppend(state, "::") && ParseName(state) &&
++      Optional(ParseDiscriminator(state))) {
+     return true;
+   }
+-  *state = copy;
+ 
++  // Since we're not going to overwrite the above "::" by re-parsing the
++  // <encoding> (whose trailing '\0' byte was in the byte now holding the
++  // first ':'), we have to rollback the "::" if the <name> parse failed.
++  if (state->parse_state.append) {
++    state->out[state->parse_state.out_cur_idx - 2] = '\0';
++  }
++
++  return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
++}
++
++static bool ParseLocalName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+-      ParseTwoCharToken(state, "Es") && Optional(ParseDiscriminator(state))) {
++      ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <discriminator> := _ <(non-negative) number>
+ static bool ParseDiscriminator(State *state) {
+-  State copy = *state;
+-  if (ParseOneCharToken(state, '_') && ParseNumber(state, NULL)) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
++  ParseState copy = state->parse_state;
++  if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // <substitution> ::= S_
+ //                ::= S <seq-id> _
+ //                ::= St, etc.
+-static bool ParseSubstitution(State *state) {
++//
++// "St" is special in that it's not valid as a standalone name, and it *is*
++// allowed to precede a name without being wrapped in "N...E".  This means that
++// if we accept it on its own, we can accept "St1a" and try to parse
++// template-args, then fail and backtrack, accept "St" on its own, then "1a" as
++// an unqualified name and re-parse the same template-args.  To block this
++// exponential backtracking, we disable it with 'accept_std=false' in
++// problematic contexts.
++static bool ParseSubstitution(State *state, bool accept_std) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseTwoCharToken(state, "S_")) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+   }
+ 
+-  State copy = *state;
++  ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'S') && ParseSeqId(state) &&
+       ParseOneCharToken(state, '_')) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+   }
+-  *state = copy;
++  state->parse_state = copy;
+ 
+   // Expand abbreviations like "St" => "std".
+   if (ParseOneCharToken(state, 'S')) {
+     const AbbrevPair *p;
+-    for (p = kSubstitutionList; p->abbrev != NULL; ++p) {
+-      if (state->mangled_cur[0] == p->abbrev[1]) {
++    for (p = kSubstitutionList; p->abbrev != nullptr; ++p) {
++      if (RemainingInput(state)[0] == p->abbrev[1] &&
++          (accept_std || p->abbrev[1] != 't')) {
+         MaybeAppend(state, "std");
+         if (p->real_name[0] != '\0') {
+           MaybeAppend(state, "::");
+           MaybeAppend(state, p->real_name);
+         }
+-        ++state->mangled_cur;
++        ++state->parse_state.mangled_idx;
+         return true;
+       }
+     }
+   }
+-  *state = copy;
++  state->parse_state = copy;
+   return false;
+ }
+ 
+ // Parse <mangled-name>, optionally followed by either a function-clone suffix
+ // or version suffix.  Returns true only if all of "mangled_cur" was consumed.
+ static bool ParseTopLevelMangledName(State *state) {
++  ComplexityGuard guard(state);
++  if (guard.IsTooComplex()) return false;
+   if (ParseMangledName(state)) {
+-    if (state->mangled_cur[0] != '\0') {
++    if (RemainingInput(state)[0] != '\0') {
+       // Drop trailing function clone suffix, if any.
+-      if (IsFunctionCloneSuffix(state->mangled_cur)) {
++      if (IsFunctionCloneSuffix(RemainingInput(state))) {
+         return true;
+       }
+       // Append trailing version suffix if any.
+       // ex. _Z3foo@@GLIBCXX_3.4
+-      if (state->mangled_cur[0] == '@') {
+-        MaybeAppend(state, state->mangled_cur);
++      if (RemainingInput(state)[0] == '@') {
++        MaybeAppend(state, RemainingInput(state));
+         return true;
+       }
+       return false;  // Unconsumed suffix.
+@@ -1320,6 +1990,10 @@ static bool ParseTopLevelMangledName(State *state) {
+   }
+   return false;
+ }
++
++static bool Overflowed(const State *state) {
++  return state->parse_state.out_cur_idx >= state->out_end_idx;
++}
+ #endif
+ 
+ // The demangler entry point.
+@@ -1356,7 +2030,8 @@ bool Demangle(const char *mangled, char *out, size_t out_size) {
+ #else
+   State state;
+   InitState(&state, mangled, out, out_size);
+-  return ParseTopLevelMangledName(&state) && !state.overflowed;
++  return ParseTopLevelMangledName(&state) && !Overflowed(&state) &&
++         state.parse_state.out_cur_idx > 0;
+ #endif
+ }
+ 
+diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
+index 416f7ee153560..26e821a53c2cb 100644
+--- a/base/third_party/symbolize/demangle.h
++++ b/base/third_party/symbolize/demangle.h
+@@ -70,6 +70,8 @@
+ #ifndef BASE_DEMANGLE_H_
+ #define BASE_DEMANGLE_H_
+ 
++#include <stddef.h>
++
+ #include "config.h"
+ #include "glog/logging.h"
+ 
diff --git a/base/third_party/symbolize/patches/010-clang-format.patch b/base/third_party/symbolize/patches/010-clang-format.patch
new file mode 100644
index 0000000..3e786f0
--- /dev/null
+++ b/base/third_party/symbolize/patches/010-clang-format.patch
@@ -0,0 +1,1184 @@
+diff --git a/base/third_party/symbolize/demangle.cc b/base/third_party/symbolize/demangle.cc
+index 2632646dd4072..8db75f01071e2 100644
+--- a/base/third_party/symbolize/demangle.cc
++++ b/base/third_party/symbolize/demangle.cc
+@@ -139,8 +139,8 @@ static const AbbrevPair kBuiltinTypeList[] = {
+     {"g", "__float128", 0},
+     {"z", "ellipsis", 0},
+ 
+-    {"De", "decimal128", 0},      // IEEE 754r decimal floating point (128 bits)
+-    {"Dd", "decimal64", 0},       // IEEE 754r decimal floating point (64 bits)
++    {"De", "decimal128", 0},  // IEEE 754r decimal floating point (128 bits)
++    {"Dd", "decimal64", 0},   // IEEE 754r decimal floating point (64 bits)
+     {"Dc", "decltype(auto)", 0},
+     {"Da", "auto", 0},
+     {"Dn", "std::nullptr_t", 0},  // i.e., decltype(nullptr)
+@@ -148,7 +148,7 @@ static const AbbrevPair kBuiltinTypeList[] = {
+     {"Di", "char32_t", 0},
+     {"Du", "char8_t", 0},
+     {"Ds", "char16_t", 0},
+-    {"Dh", "float16", 0},         // IEEE 754r half-precision float (16 bits)
++    {"Dh", "float16", 0},  // IEEE 754r half-precision float (16 bits)
+     {nullptr, nullptr, 0},
+ };
+ 
+@@ -193,8 +193,8 @@ static_assert(sizeof(ParseState) == 4 * sizeof(int),
+ // Only one copy of this exists for each call to Demangle, so the size of this
+ // struct is nearly inconsequential.
+ typedef struct {
+-  const char *mangled_begin;  // Beginning of input string.
+-  char *out;                  // Beginning of output string.
++  const char* mangled_begin;  // Beginning of input string.
++  char* out;                  // Beginning of output string.
+   int out_end_idx;            // One past last allowed output character.
+   int recursion_depth;        // For stack exhaustion prevention.
+   int steps;               // Cap how much work we'll do, regardless of depth.
+@@ -206,7 +206,7 @@ namespace {
+ // Also prevent unbounded handling of complex inputs.
+ class ComplexityGuard {
+  public:
+-  explicit ComplexityGuard(State *state) : state_(state) {
++  explicit ComplexityGuard(State* state) : state_(state) {
+     ++state->recursion_depth;
+     ++state->steps;
+   }
+@@ -239,7 +239,7 @@ class ComplexityGuard {
+   }
+ 
+  private:
+-  State *state_;
++  State* state_;
+ };
+ }  // namespace
+ 
+@@ -255,7 +255,7 @@ static size_t StrLen(const char *str) {
+ }
+ 
+ // Returns true if "str" has at least "n" characters remaining.
+-static bool AtLeastNumCharsRemaining(const char *str, size_t n) {
++static bool AtLeastNumCharsRemaining(const char* str, size_t n) {
+   for (size_t i = 0; i < n; ++i) {
+     if (str[i] == '\0') {
+       return false;
+@@ -291,7 +291,7 @@ static void InitState(State* state,
+   state->parse_state.append = true;
+ }
+ 
+-static inline const char *RemainingInput(State *state) {
++static inline const char* RemainingInput(State* state) {
+   return &state->mangled_begin[state->parse_state.mangled_idx];
+ }
+ 
+@@ -300,7 +300,9 @@ static inline const char *RemainingInput(State *state) {
+ // not contain '\0'.
+ static bool ParseOneCharToken(State *state, const char one_char_token) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == one_char_token) {
+     ++state->parse_state.mangled_idx;
+     return true;
+@@ -313,7 +315,9 @@ static bool ParseOneCharToken(State *state, const char one_char_token) {
+ // not contain '\0'.
+ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == two_char_token[0] &&
+       RemainingInput(state)[1] == two_char_token[1]) {
+     state->parse_state.mangled_idx += 2;
+@@ -326,7 +330,9 @@ static bool ParseTwoCharToken(State *state, const char *two_char_token) {
+ // "char_class" at "mangled_cur" position.
+ static bool ParseCharClass(State *state, const char *char_class) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (RemainingInput(state)[0] == '\0') {
+     return false;
+   }
+@@ -340,7 +346,7 @@ static bool ParseCharClass(State *state, const char *char_class) {
+   return false;
+ }
+ 
+-static bool ParseDigit(State *state, int *digit) {
++static bool ParseDigit(State* state, int* digit) {
+   char c = RemainingInput(state)[0];
+   if (ParseCharClass(state, "0123456789")) {
+     if (digit != nullptr) {
+@@ -352,7 +358,9 @@ static bool ParseDigit(State *state, int *digit) {
+ }
+ 
+ // This function is used for handling an optional non-terminal.
+-static bool Optional(bool /*status*/) { return true; }
++static bool Optional(bool /*status*/) {
++  return true;
++}
+ 
+ // This function is used for handling <non-terminal>+ syntax.
+ typedef bool (*ParseFunc)(State *);
+@@ -378,7 +386,7 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) {
+ // Append "str" at "out_cur_idx".  If there is an overflow, out_cur_idx is
+ // set to out_end_idx+1.  The output string is ensured to
+ // always terminate with '\0' as long as there is no overflow.
+-static void Append(State *state, const char *const str, const size_t length) {
++static void Append(State* state, const char* const str, const size_t length) {
+   for (size_t i = 0; i < length; ++i) {
+     if (state->parse_state.out_cur_idx + 1 <
+         state->out_end_idx) {  // +1 for '\0'
+@@ -396,13 +404,17 @@ static void Append(State *state, const char *const str, const size_t length) {
+ }
+ 
+ // We don't use equivalents in libc to avoid locale issues.
+-static bool IsLower(char c) { return c >= 'a' && c <= 'z'; }
++static bool IsLower(char c) {
++  return c >= 'a' && c <= 'z';
++}
+ 
+ static bool IsAlpha(char c) {
+   return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+ 
+-static bool IsDigit(char c) { return c >= '0' && c <= '9'; }
++static bool IsDigit(char c) {
++  return c >= '0' && c <= '9';
++}
+ 
+ // Returns true if "str" is a function clone suffix.  These suffixes are used
+ // by GCC 4.5.x and later versions (and our locally-modified version of GCC
+@@ -428,20 +440,22 @@ static bool IsFunctionCloneSuffix(const char *str) {
+         ++i;
+       }
+     }
+-    if (!parsed)
++    if (!parsed) {
+       return false;
++    }
+   }
+   return true;  // Consumed everything in "str".
+ }
+ 
+-static bool EndsWith(State *state, const char chr) {
++static bool EndsWith(State* state, const char chr) {
+   return state->parse_state.out_cur_idx > 0 &&
+          state->parse_state.out_cur_idx < state->out_end_idx &&
+          chr == state->out[state->parse_state.out_cur_idx - 1];
+ }
+ 
+ // Append "str" with some tweaks, iff "append" state is true.
+-static void MaybeAppendWithLength(State *state, const char *const str,
++static void MaybeAppendWithLength(State* state,
++                                  const char* const str,
+                                   const size_t length) {
+   if (state->parse_state.append && length > 0) {
+     // Append a space if the output buffer ends with '<' and "str"
+@@ -461,7 +475,7 @@ static void MaybeAppendWithLength(State *state, const char *const str,
+ }
+ 
+ // Appends a positive decimal number to the output if appending is enabled.
+-static bool MaybeAppendDecimal(State *state, int val) {
++static bool MaybeAppendDecimal(State* state, int val) {
+   // Max {32-64}-bit unsigned int is 20 digits.
+   constexpr size_t kMaxLength = 20;
+   char buf[kMaxLength];
+@@ -471,7 +485,7 @@ static bool MaybeAppendDecimal(State *state, int val) {
+   if (state->parse_state.append) {
+     // We can't have a one-before-the-beginning pointer, so instead start with
+     // one-past-the-end and manipulate one character before the pointer.
+-    char *p = &buf[kMaxLength];
++    char* p = &buf[kMaxLength];
+     do {  // val=0 is the only input that should write a leading zero digit.
+       *--p = static_cast<char>((val % 10) + '0');
+       val /= 10;
+@@ -486,7 +500,7 @@ static bool MaybeAppendDecimal(State *state, int val) {
+ 
+ // A convenient wrapper around MaybeAppendWithLength().
+ // Returns true so that it can be placed in "if" conditions.
+-static bool MaybeAppend(State *state, const char *const str) {
++static bool MaybeAppend(State* state, const char* const str) {
+   if (state->parse_state.append) {
+     size_t length = StrLen(str);
+     MaybeAppendWithLength(state, str, length);
+@@ -501,7 +515,7 @@ static bool EnterNestedName(State *state) {
+ }
+ 
+ // This function is used for handling nested names.
+-static bool LeaveNestedName(State *state, int16_t prev_value) {
++static bool LeaveNestedName(State* state, int16_t prev_value) {
+   state->parse_state.nest_level = prev_value;
+   return true;
+ }
+@@ -543,7 +557,7 @@ static void MaybeCancelLastSeparator(State *state) {
+ 
+ // Returns true if the identifier of the given length pointed to by
+ // "mangled_cur" is anonymous namespace.
+-static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
++static bool IdentifierIsAnonymousNamespace(State* state, size_t length) {
+   // Returns true if "anon_prefix" is a proper prefix of "mangled_cur".
+   static const char anon_prefix[] = "_GLOBAL__N_";
+   return (length > (sizeof(anon_prefix) - 1) &&
+@@ -554,25 +568,25 @@ static bool IdentifierIsAnonymousNamespace(State *state, size_t length) {
+ static bool ParseMangledName(State *state);
+ static bool ParseEncoding(State *state);
+ static bool ParseName(State *state);
+-static bool ParseUnscopedName(State *state);
++static bool ParseUnscopedName(State* state);
+ static bool ParseNestedName(State *state);
+ static bool ParsePrefix(State *state);
+ static bool ParseUnqualifiedName(State *state);
+ static bool ParseSourceName(State *state);
+ static bool ParseLocalSourceName(State *state);
+-static bool ParseUnnamedTypeName(State *state);
++static bool ParseUnnamedTypeName(State* state);
+ static bool ParseNumber(State *state, int *number_out);
+ static bool ParseFloatNumber(State *state);
+ static bool ParseSeqId(State *state);
+-static bool ParseIdentifier(State *state, size_t length);
+-static bool ParseOperatorName(State *state, int *arity);
++static bool ParseIdentifier(State* state, size_t length);
++static bool ParseOperatorName(State* state, int* arity);
+ static bool ParseSpecialName(State *state);
+ static bool ParseCallOffset(State *state);
+ static bool ParseNVOffset(State *state);
+ static bool ParseVOffset(State *state);
+-static bool ParseAbiTags(State *state);
++static bool ParseAbiTags(State* state);
+ static bool ParseCtorDtorName(State *state);
+-static bool ParseDecltype(State *state);
++static bool ParseDecltype(State* state);
+ static bool ParseType(State *state);
+ static bool ParseCVQualifiers(State *state);
+ static bool ParseBuiltinType(State *state);
+@@ -585,15 +599,15 @@ static bool ParseTemplateParam(State *state);
+ static bool ParseTemplateTemplateParam(State *state);
+ static bool ParseTemplateArgs(State *state);
+ static bool ParseTemplateArg(State *state);
+-static bool ParseBaseUnresolvedName(State *state);
+-static bool ParseUnresolvedName(State *state);
++static bool ParseBaseUnresolvedName(State* state);
++static bool ParseUnresolvedName(State* state);
+ static bool ParseExpression(State *state);
+ static bool ParseExprPrimary(State *state);
+-static bool ParseExprCastValue(State *state);
++static bool ParseExprCastValue(State* state);
+ static bool ParseLocalName(State *state);
+-static bool ParseLocalNameSuffix(State *state);
++static bool ParseLocalNameSuffix(State* state);
+ static bool ParseDiscriminator(State *state);
+-static bool ParseSubstitution(State *state, bool accept_std);
++static bool ParseSubstitution(State* state, bool accept_std);
+ 
+ // Implementation note: the following code is a straightforward
+ // translation of the Itanium C++ ABI defined in BNF with a couple of
+@@ -629,7 +643,9 @@ static bool ParseSubstitution(State *state, bool accept_std);
+ // <mangled-name> ::= _Z <encoding>
+ static bool ParseMangledName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseTwoCharToken(state, "_Z") && ParseEncoding(state);
+ }
+ 
+@@ -638,7 +654,9 @@ static bool ParseMangledName(State *state) {
+ //            ::= <special-name>
+ static bool ParseEncoding(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   // Implementing the first two productions together as <name>
+   // [<bare-function-type>] avoids exponential blowup of backtracking.
+   //
+@@ -660,7 +678,9 @@ static bool ParseEncoding(State *state) {
+ //        ::= <local-name>
+ static bool ParseName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseNestedName(state) || ParseLocalName(state)) {
+     return true;
+   }
+@@ -690,7 +710,9 @@ static bool ParseName(State *state) {
+ //                 ::= St <unqualified-name>
+ static bool ParseUnscopedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseUnqualifiedName(state)) {
+     return true;
+   }
+@@ -706,7 +728,7 @@ static bool ParseUnscopedName(State *state) {
+ 
+ // <ref-qualifer> ::= R // lvalue method reference qualifier
+ //                ::= O // rvalue method reference qualifier
+-static inline bool ParseRefQualifier(State *state) {
++static inline bool ParseRefQualifier(State* state) {
+   return ParseCharClass(state, "OR");
+ }
+ 
+@@ -716,7 +738,9 @@ static inline bool ParseRefQualifier(State *state) {
+ //                   <template-args> E
+ static bool ParseNestedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'N') && EnterNestedName(state) &&
+       Optional(ParseCVQualifiers(state)) &&
+@@ -742,7 +766,9 @@ static bool ParseNestedName(State *state) {
+ //                   ::= <substitution>
+ static bool ParsePrefix(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   bool has_something = false;
+   while (true) {
+     MaybeAppendSeparator(state);
+@@ -773,7 +799,9 @@ static bool ParsePrefix(State *state) {
+ // <local-source-name> is a GCC extension; see below.
+ static bool ParseUnqualifiedName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) ||
+       ParseSourceName(state) || ParseLocalSourceName(state) ||
+       ParseUnnamedTypeName(state)) {
+@@ -784,9 +812,11 @@ static bool ParseUnqualifiedName(State *state) {
+ 
+ // <abi-tags> ::= <abi-tag> [<abi-tags>]
+ // <abi-tag>  ::= B <source-name>
+-static bool ParseAbiTags(State *state) {
++static bool ParseAbiTags(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   while (ParseOneCharToken(state, 'B')) {
+     ParseState copy = state->parse_state;
+@@ -805,7 +835,9 @@ static bool ParseAbiTags(State *state) {
+ // <source-name> ::= <positive length number> <identifier>
+ static bool ParseSourceName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   int length = -1;
+   if (ParseNumber(state, &length) &&
+@@ -823,7 +855,9 @@ static bool ParseSourceName(State *state) {
+ //   https://gcc.gnu.org/viewcvs?view=rev&revision=124467
+ static bool ParseLocalSourceName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'L') && ParseSourceName(state) &&
+       Optional(ParseDiscriminator(state))) {
+@@ -837,9 +871,11 @@ static bool ParseLocalSourceName(State *state) {
+ //                     ::= <closure-type-name>
+ // <closure-type-name> ::= Ul <lambda-sig> E [<(nonnegative) number>] _
+ // <lambda-sig>        ::= <(parameter) type>+
+-static bool ParseUnnamedTypeName(State *state) {
++static bool ParseUnnamedTypeName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   // Type's 1-based index n is encoded as { "", n == 1; itoa(n-2), otherwise }.
+   // Optionally parse the encoded value into 'which' and add 2 to get the index.
+@@ -878,12 +914,14 @@ static bool ParseUnnamedTypeName(State *state) {
+ // parsed number on success.
+ static bool ParseNumber(State *state, int *number_out) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   bool negative = false;
+   if (ParseOneCharToken(state, 'n')) {
+     negative = true;
+   }
+-  const char *p = RemainingInput(state);
++  const char* p = RemainingInput(state);
+   uint64_t number = 0;
+   for (; *p != '\0'; ++p) {
+     if (IsDigit(*p)) {
+@@ -913,8 +951,10 @@ static bool ParseNumber(State *state, int *number_out) {
+ // hexadecimal string.
+ static bool ParseFloatNumber(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
+-  const char *p = RemainingInput(state);
++  if (guard.IsTooComplex()) {
++    return false;
++  }
++  const char* p = RemainingInput(state);
+   for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'a' && *p <= 'f')) {
+       break;
+@@ -931,8 +971,10 @@ static bool ParseFloatNumber(State *state) {
+ // using digits and upper case letters
+ static bool ParseSeqId(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
+-  const char *p = RemainingInput(state);
++  if (guard.IsTooComplex()) {
++    return false;
++  }
++  const char* p = RemainingInput(state);
+   for (; *p != '\0'; ++p) {
+     if (!IsDigit(*p) && !(*p >= 'A' && *p <= 'Z')) {
+       break;
+@@ -946,9 +988,11 @@ static bool ParseSeqId(State *state) {
+ }
+ 
+ // <identifier> ::= <unqualified source code identifier> (of given length)
+-static bool ParseIdentifier(State *state, size_t length) {
++static bool ParseIdentifier(State* state, size_t length) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) {
+     return false;
+   }
+@@ -964,9 +1008,11 @@ static bool ParseIdentifier(State *state, size_t length) {
+ // <operator-name> ::= nw, and other two letters cases
+ //                 ::= cv <type>  # (cast)
+ //                 ::= v  <digit> <source-name> # vendor extended operator
+-static bool ParseOperatorName(State *state, int *arity) {
++static bool ParseOperatorName(State* state, int* arity) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (!AtLeastNumCharsRemaining(RemainingInput(state), 2)) {
+     return false;
+   }
+@@ -1036,7 +1082,9 @@ static bool ParseOperatorName(State *state, int *arity) {
+ // stack traces.  The are special data.
+ static bool ParseSpecialName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'T') && ParseCharClass(state, "VTISH") &&
+       ParseType(state)) {
+@@ -1098,7 +1146,9 @@ static bool ParseSpecialName(State *state) {
+ //               ::= v <v-offset> _
+ static bool ParseCallOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'h') && ParseNVOffset(state) &&
+       ParseOneCharToken(state, '_')) {
+@@ -1118,14 +1168,18 @@ static bool ParseCallOffset(State *state) {
+ // <nv-offset> ::= <(offset) number>
+ static bool ParseNVOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseNumber(state, nullptr);
+ }
+ 
+ // <v-offset>  ::= <(offset) number> _ <(virtual offset) number>
+ static bool ParseVOffset(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseNumber(state, nullptr) && ParseOneCharToken(state, '_') &&
+       ParseNumber(state, nullptr)) {
+@@ -1144,11 +1198,13 @@ static bool ParseVOffset(State *state) {
+ //                  ::= C4 | D4
+ static bool ParseCtorDtorName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'C')) {
+     if (ParseCharClass(state, "1234")) {
+-      const char *const prev_name =
++      const char* const prev_name =
+           state->out + state->parse_state.prev_name_idx;
+       MaybeAppendWithLength(state, prev_name,
+                             state->parse_state.prev_name_length);
+@@ -1161,7 +1217,7 @@ static bool ParseCtorDtorName(State *state) {
+   state->parse_state = copy;
+ 
+   if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "0124")) {
+-    const char *const prev_name = state->out + state->parse_state.prev_name_idx;
++    const char* const prev_name = state->out + state->parse_state.prev_name_idx;
+     MaybeAppend(state, "~");
+     MaybeAppendWithLength(state, prev_name,
+                           state->parse_state.prev_name_length);
+@@ -1174,9 +1230,11 @@ static bool ParseCtorDtorName(State *state) {
+ // <decltype> ::= Dt <expression> E  # decltype of an id-expression or class
+ //                                   # member access (C++0x)
+ //            ::= DT <expression> E  # decltype of an expression (C++0x)
+-static bool ParseDecltype(State *state) {
++static bool ParseDecltype(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'D') && ParseCharClass(state, "tT") &&
+@@ -1209,7 +1267,9 @@ static bool ParseDecltype(State *state) {
+ //
+ static bool ParseType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+ 
+   // We should check CV-qualifers, and PRGC things first.
+@@ -1227,7 +1287,9 @@ static bool ParseType(State *state) {
+   // By consuming the CV-qualifiers first, the former parse is disabled.
+   if (ParseCVQualifiers(state)) {
+     const bool result = ParseType(state);
+-    if (!result) state->parse_state = copy;
++    if (!result) {
++      state->parse_state = copy;
++    }
+     return result;
+   }
+   state->parse_state = copy;
+@@ -1238,7 +1300,9 @@ static bool ParseType(State *state) {
+   // refusing to backtrack the tag characters.
+   if (ParseCharClass(state, "OPRCG")) {
+     const bool result = ParseType(state);
+-    if (!result) state->parse_state = copy;
++    if (!result) {
++      state->parse_state = copy;
++    }
+     return result;
+   }
+   state->parse_state = copy;
+@@ -1286,7 +1350,9 @@ static bool ParseType(State *state) {
+ // ParseType().
+ static bool ParseCVQualifiers(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   int num_cv_qualifiers = 0;
+   num_cv_qualifiers += ParseOneCharToken(state, 'r');
+   num_cv_qualifiers += ParseOneCharToken(state, 'V');
+@@ -1303,7 +1369,9 @@ static bool ParseCVQualifiers(State *state) {
+ //
+ static bool ParseBuiltinType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   const AbbrevPair *p;
+   for (p = kBuiltinTypeList; p->abbrev != nullptr; ++p) {
+     // Guaranteed only 1- or 2-character strings in kBuiltinTypeList.
+@@ -1333,11 +1401,15 @@ static bool ParseBuiltinType(State *state) {
+ //                                           noexcept
+ //                   ::= Dw <type>+ E      # dynamic exception specification
+ //                                           with instantiation-dependent types
+-static bool ParseExceptionSpec(State *state) {
++static bool ParseExceptionSpec(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+-  if (ParseTwoCharToken(state, "Do")) return true;
++  if (ParseTwoCharToken(state, "Do")) {
++    return true;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (ParseTwoCharToken(state, "DO") && ParseExpression(state) &&
+@@ -1357,7 +1429,9 @@ static bool ParseExceptionSpec(State *state) {
+ // <function-type> ::= [exception-spec] F [Y] <bare-function-type> [O] E
+ static bool ParseFunctionType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (Optional(ParseExceptionSpec(state)) && ParseOneCharToken(state, 'F') &&
+       Optional(ParseOneCharToken(state, 'Y')) && ParseBareFunctionType(state) &&
+@@ -1372,7 +1446,9 @@ static bool ParseFunctionType(State *state) {
+ // <bare-function-type> ::= <(signature) type>+
+ static bool ParseBareFunctionType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (OneOrMore(ParseType, state)) {
+@@ -1387,7 +1463,9 @@ static bool ParseBareFunctionType(State *state) {
+ // <class-enum-type> ::= <name>
+ static bool ParseClassEnumType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return ParseName(state);
+ }
+ 
+@@ -1395,7 +1473,9 @@ static bool ParseClassEnumType(State *state) {
+ //              ::= A [<(dimension) expression>] _ <(element) type>
+ static bool ParseArrayType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'A') && ParseNumber(state, nullptr) &&
+       ParseOneCharToken(state, '_') && ParseType(state)) {
+@@ -1414,7 +1494,9 @@ static bool ParseArrayType(State *state) {
+ // <pointer-to-member-type> ::= M <(class) type> <(member) type>
+ static bool ParsePointerToMemberType(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'M') && ParseType(state) && ParseType(state)) {
+     return true;
+@@ -1427,7 +1509,9 @@ static bool ParsePointerToMemberType(State *state) {
+ //                  ::= T <parameter-2 non-negative number> _
+ static bool ParseTemplateParam(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTwoCharToken(state, "T_")) {
+     MaybeAppend(state, "?");  // We don't support template substitutions.
+     return true;
+@@ -1447,7 +1531,9 @@ static bool ParseTemplateParam(State *state) {
+ //                           ::= <substitution>
+ static bool ParseTemplateTemplateParam(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   return (ParseTemplateParam(state) ||
+           // "std" on its own isn't a template.
+           ParseSubstitution(state, /*accept_std=*/false));
+@@ -1456,7 +1542,9 @@ static bool ParseTemplateTemplateParam(State *state) {
+ // <template-args> ::= I <template-arg>+ E
+ static bool ParseTemplateArgs(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   DisableAppend(state);
+   if (ParseOneCharToken(state, 'I') && OneOrMore(ParseTemplateArg, state) &&
+@@ -1475,7 +1563,9 @@ static bool ParseTemplateArgs(State *state) {
+ //                 ::= X <expression> E
+ static bool ParseTemplateArg(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'J') && ZeroOrMore(ParseTemplateArg, state) &&
+       ParseOneCharToken(state, 'E')) {
+@@ -1578,14 +1668,14 @@ static bool ParseTemplateArg(State *state) {
+ // <unresolved-type> ::= <template-param> [<template-args>]
+ //                   ::= <decltype>
+ //                   ::= <substitution>
+-static inline bool ParseUnresolvedType(State *state) {
++static inline bool ParseUnresolvedType(State* state) {
+   // No ComplexityGuard because we don't copy the state in this stack frame.
+   return (ParseTemplateParam(state) && Optional(ParseTemplateArgs(state))) ||
+          ParseDecltype(state) || ParseSubstitution(state, /*accept_std=*/false);
+ }
+ 
+ // <simple-id> ::= <source-name> [<template-args>]
+-static inline bool ParseSimpleId(State *state) {
++static inline bool ParseSimpleId(State* state) {
+   // No ComplexityGuard because we don't copy the state in this stack frame.
+ 
+   // Note: <simple-id> cannot be followed by a parameter pack; see comment in
+@@ -1596,9 +1686,11 @@ static inline bool ParseSimpleId(State *state) {
+ // <base-unresolved-name> ::= <source-name> [<template-args>]
+ //                        ::= on <operator-name> [<template-args>]
+ //                        ::= dn <destructor-name>
+-static bool ParseBaseUnresolvedName(State *state) {
++static bool ParseBaseUnresolvedName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   if (ParseSimpleId(state)) {
+     return true;
+@@ -1626,9 +1718,11 @@ static bool ParseBaseUnresolvedName(State *state) {
+ //                         <base-unresolved-name>
+ //                   ::= [gs] sr <unresolved-qualifier-level>+ E
+ //                         <base-unresolved-name>
+-static bool ParseUnresolvedName(State *state) {
++static bool ParseUnresolvedName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   ParseState copy = state->parse_state;
+   if (Optional(ParseTwoCharToken(state, "gs")) &&
+@@ -1684,7 +1778,9 @@ static bool ParseUnresolvedName(State *state) {
+ //                  ::= fL <number> p <(top-level) CV-qualifiers> <number> _
+ static bool ParseExpression(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTemplateParam(state) || ParseExprPrimary(state)) {
+     return true;
+   }
+@@ -1817,7 +1913,9 @@ static bool ParseExpression(State *state) {
+ // casts to <local-name> types.
+ static bool ParseExprPrimary(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+ 
+   // The "LZ" special case: if we see LZ, we commit to accept "LZ <encoding> E"
+@@ -1848,9 +1946,11 @@ static bool ParseExprPrimary(State *state) {
+ }
+ 
+ // <number> or <float>, followed by 'E', as described above ParseExprPrimary.
+-static bool ParseExprCastValue(State *state) {
++static bool ParseExprCastValue(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   // We have to be able to backtrack after accepting a number because we could
+   // have e.g. "7fffE", which will accept "7" as a number but then fail to find
+   // the 'E'.
+@@ -1877,9 +1977,11 @@ static bool ParseExprCastValue(State *state) {
+ //   <local-name-suffix> ::= s [<discriminator>]
+ //                       ::= <name> [<discriminator>]
+ 
+-static bool ParseLocalNameSuffix(State *state) {
++static bool ParseLocalNameSuffix(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+ 
+   if (MaybeAppend(state, "::") && ParseName(state) &&
+       Optional(ParseDiscriminator(state))) {
+@@ -1896,9 +1998,11 @@ static bool ParseLocalNameSuffix(State *state) {
+   return ParseOneCharToken(state, 's') && Optional(ParseDiscriminator(state));
+ }
+ 
+-static bool ParseLocalName(State *state) {
++static bool ParseLocalName(State* state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, 'Z') && ParseEncoding(state) &&
+       ParseOneCharToken(state, 'E') && ParseLocalNameSuffix(state)) {
+@@ -1911,7 +2015,9 @@ static bool ParseLocalName(State *state) {
+ // <discriminator> := _ <(non-negative) number>
+ static bool ParseDiscriminator(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   ParseState copy = state->parse_state;
+   if (ParseOneCharToken(state, '_') && ParseNumber(state, nullptr)) {
+     return true;
+@@ -1931,9 +2037,11 @@ static bool ParseDiscriminator(State *state) {
+ // an unqualified name and re-parse the same template-args.  To block this
+ // exponential backtracking, we disable it with 'accept_std=false' in
+ // problematic contexts.
+-static bool ParseSubstitution(State *state, bool accept_std) {
++static bool ParseSubstitution(State* state, bool accept_std) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseTwoCharToken(state, "S_")) {
+     MaybeAppend(state, "?");  // We don't support substitutions.
+     return true;
+@@ -1971,7 +2079,9 @@ static bool ParseSubstitution(State *state, bool accept_std) {
+ // or version suffix.  Returns true only if all of "mangled_cur" was consumed.
+ static bool ParseTopLevelMangledName(State *state) {
+   ComplexityGuard guard(state);
+-  if (guard.IsTooComplex()) return false;
++  if (guard.IsTooComplex()) {
++    return false;
++  }
+   if (ParseMangledName(state)) {
+     if (RemainingInput(state)[0] != '\0') {
+       // Drop trailing function clone suffix, if any.
+@@ -1991,13 +2101,13 @@ static bool ParseTopLevelMangledName(State *state) {
+   return false;
+ }
+ 
+-static bool Overflowed(const State *state) {
++static bool Overflowed(const State* state) {
+   return state->parse_state.out_cur_idx >= state->out_end_idx;
+ }
+ #endif
+ 
+ // The demangler entry point.
+-bool Demangle(const char *mangled, char *out, size_t out_size) {
++bool Demangle(const char* mangled, char* out, size_t out_size) {
+ #if defined(GLOG_OS_WINDOWS)
+ #if defined(HAVE_DBGHELP)
+   // When built with incremental linking, the Windows debugger
+diff --git a/base/third_party/symbolize/demangle.h b/base/third_party/symbolize/demangle.h
+index 26e821a53c2cb..7d5cfaaabf0dd 100644
+--- a/base/third_party/symbolize/demangle.h
++++ b/base/third_party/symbolize/demangle.h
+@@ -80,7 +80,7 @@ _START_GOOGLE_NAMESPACE_
+ // Demangle "mangled".  On success, return true and write the
+ // demangled symbol name to "out".  Otherwise, return false.
+ // "out" is modified even if demangling is unsuccessful.
+-bool GLOG_EXPORT Demangle(const char *mangled, char *out, size_t out_size);
++bool GLOG_EXPORT Demangle(const char* mangled, char* out, size_t out_size);
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+diff --git a/base/third_party/symbolize/glog/logging.h b/base/third_party/symbolize/glog/logging.h
+index 46869226024da..b935e3ec9cded 100644
+--- a/base/third_party/symbolize/glog/logging.h
++++ b/base/third_party/symbolize/glog/logging.h
+@@ -38,4 +38,4 @@
+ 
+ // Not needed in Chrome.
+ 
+-#endif // GLOG_LOGGING_H
++#endif  // GLOG_LOGGING_H
+diff --git a/base/third_party/symbolize/symbolize.cc b/base/third_party/symbolize/symbolize.cc
+index b6ddc85d57185..a3b8399f411bf 100644
+--- a/base/third_party/symbolize/symbolize.cc
++++ b/base/third_party/symbolize/symbolize.cc
+@@ -97,7 +97,7 @@ void InstallSymbolizeOpenObjectFileCallback(
+ // where the input symbol is demangled in-place.
+ // To keep stack consumption low, we would like this function to not
+ // get inlined.
+-static ATTRIBUTE_NOINLINE void DemangleInplace(char *out, size_t out_size) {
++static ATTRIBUTE_NOINLINE void DemangleInplace(char* out, size_t out_size) {
+   char demangled[256];  // Big enough for sane demangled symbols.
+   if (Demangle(out, demangled, sizeof(demangled))) {
+     // Demangling succeeded. Copy to out if the space allows.
+@@ -121,17 +121,17 @@ _END_GOOGLE_NAMESPACE_
+ #else
+ #include <elf.h>
+ #endif
++#include <fcntl.h>
++#include <stdint.h>
++#include <sys/stat.h>
++#include <sys/types.h>
++#include <unistd.h>
+ #include <cerrno>
+ #include <climits>
+ #include <cstddef>
+ #include <cstdio>
+ #include <cstdlib>
+ #include <cstring>
+-#include <fcntl.h>
+-#include <stdint.h>
+-#include <sys/stat.h>
+-#include <sys/types.h>
+-#include <unistd.h>
+ 
+ #include "symbolize.h"
+ #include "config.h"
+@@ -153,7 +153,8 @@ ssize_t ReadFromOffset(const int fd,
+                        const size_t count,
+                        const size_t offset) {
+   SAFE_ASSERT(fd >= 0);
+-  SAFE_ASSERT(count <= static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
++  SAFE_ASSERT(count <=
++              static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
+   char *buf0 = reinterpret_cast<char *>(buf);
+   size_t num_bytes = 0;
+   while (num_bytes < count) {
+@@ -176,8 +177,10 @@ ssize_t ReadFromOffset(const int fd,
+ // pointed by "fd" into the buffer starting at "buf" while handling
+ // short reads and EINTR.  On success, return true. Otherwise, return
+ // false.
+-static bool ReadFromOffsetExact(const int fd, void *buf,
+-                                const size_t count, const size_t offset) {
++static bool ReadFromOffsetExact(const int fd,
++                                void* buf,
++                                const size_t count,
++                                const size_t offset) {
+   ssize_t len = ReadFromOffset(fd, buf, count, offset);
+   return static_cast<size_t>(len) == count;
+ }
+@@ -199,9 +202,11 @@ static int FileGetElfType(const int fd) {
+ // and return true.  Otherwise, return false.
+ // To keep stack consumption low, we would like this function to not get
+ // inlined.
+-static ATTRIBUTE_NOINLINE bool
+-GetSectionHeaderByType(const int fd, ElfW(Half) sh_num, const size_t sh_offset,
+-                       ElfW(Word) type, ElfW(Shdr) *out) {
++static ATTRIBUTE_NOINLINE bool GetSectionHeaderByType(const int fd,
++                                                      ElfW(Half) sh_num,
++                                                      const size_t sh_offset,
++                                                      ElfW(Word) type,
++                                                      ElfW(Shdr) * out) {
+   // Read at most 16 section headers at a time to save read calls.
+   ElfW(Shdr) buf[16];
+   for (size_t i = 0; i < sh_num;) {
+@@ -248,8 +253,8 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
+   }
+ 
+   for (size_t i = 0; i < elf_header.e_shnum; ++i) {
+-    size_t section_header_offset = (elf_header.e_shoff +
+-                                   elf_header.e_shentsize * i);
++    size_t section_header_offset =
++        (elf_header.e_shoff + elf_header.e_shentsize * i);
+     if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) {
+       return false;
+     }
+@@ -281,10 +286,13 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len,
+ // to out.  Otherwise, return false.
+ // To keep stack consumption low, we would like this function to not get
+ // inlined.
+-static ATTRIBUTE_NOINLINE bool
+-FindSymbol(uint64_t pc, const int fd, char *out, size_t out_size,
+-           uint64_t symbol_offset, const ElfW(Shdr) *strtab,
+-           const ElfW(Shdr) *symtab) {
++static ATTRIBUTE_NOINLINE bool FindSymbol(uint64_t pc,
++                                          const int fd,
++                                          char* out,
++                                          size_t out_size,
++                                          uint64_t symbol_offset,
++                                          const ElfW(Shdr) * strtab,
++                                          const ElfW(Shdr) * symtab) {
+   if (symtab == NULL) {
+     return false;
+   }
+@@ -384,7 +392,7 @@ namespace {
+ // and snprintf().
+ class LineReader {
+  public:
+-  explicit LineReader(int fd, char *buf, size_t buf_len, size_t offset)
++  explicit LineReader(int fd, char* buf, size_t buf_len, size_t offset)
+       : fd_(fd),
+         buf_(buf),
+         buf_len_(buf_len),
+@@ -449,11 +457,12 @@ class LineReader {
+   }
+ 
+  private:
+-  LineReader(const LineReader &);
++  LineReader(const LineReader&);
+   void operator=(const LineReader&);
+ 
+   char *FindLineFeed() {
+-    return reinterpret_cast<char *>(memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
++    return reinterpret_cast<char*>(
++        memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_)));
+   }
+ 
+   bool BufferIsEmpty() {
+@@ -483,7 +492,8 @@ static char *GetHex(const char *start, const char *end, uint64_t *hex) {
+     int ch = *p;
+     if ((ch >= '0' && ch <= '9') ||
+         (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) {
+-      *hex = (*hex << 4U) | (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
++      *hex = (*hex << 4U) |
++             (ch < 'A' ? static_cast<uint64_t>(ch - '0') : (ch & 0xF) + 9U);
+     } else {  // Encountered the first non-hex character.
+       break;
+     }
+@@ -647,12 +657,11 @@ static int OpenObjectFileContainingPcAndGetStartAddressNoHook(
+   }
+ }
+ 
+-int OpenObjectFileContainingPcAndGetStartAddress(
+-    uint64_t pc,
+-    uint64_t& start_address,
+-    uint64_t& base_address,
+-    char* out_file_name,
+-    size_t out_file_name_size) {
++int OpenObjectFileContainingPcAndGetStartAddress(uint64_t pc,
++                                                 uint64_t& start_address,
++                                                 uint64_t& base_address,
++                                                 char* out_file_name,
++                                                 size_t out_file_name_size) {
+   if (g_symbolize_open_object_file_callback) {
+     return g_symbolize_open_object_file_callback(
+         pc, start_address, base_address, out_file_name, out_file_name_size);
+@@ -668,7 +677,11 @@ int OpenObjectFileContainingPcAndGetStartAddress(
+ // bytes. Output will be truncated as needed, and a NUL character is always
+ // appended.
+ // NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
+-static char *itoa_r(uintptr_t i, char *buf, size_t sz, unsigned base, size_t padding) {
++static char* itoa_r(uintptr_t i,
++                    char* buf,
++                    size_t sz,
++                    unsigned base,
++                    size_t padding) {
+   // Make sure we can write at least one NUL byte.
+   size_t n = 1;
+   if (n > sz) {
+@@ -745,7 +758,8 @@ static void SafeAppendHexNumber(uint64_t value, char* dest, size_t dest_size) {
+ // and "out" is used as its output.
+ // To keep stack consumption low, we would like this function to not
+ // get inlined.
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   uint64_t pc0 = reinterpret_cast<uintptr_t>(pc);
+   uint64_t start_address = 0;
+@@ -822,14 +836,16 @@ static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+-#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && defined(HAVE_DLADDR)
++#elif (defined(GLOG_OS_MACOSX) || defined(GLOG_OS_EMSCRIPTEN)) && \
++    defined(HAVE_DLADDR)
+ 
+ #include <dlfcn.h>
+ #include <cstring>
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   Dl_info info;
+   if (dladdr(pc, &info)) {
+@@ -883,7 +899,8 @@ private:
+   SymInitializer& operator=(const SymInitializer&);
+ };
+ 
+-static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void *pc, char *out,
++static ATTRIBUTE_NOINLINE bool SymbolizeAndDemangle(void* pc,
++                                                    char* out,
+                                                     size_t out_size) {
+   const static SymInitializer symInitializer;
+   if (!symInitializer.ready) {
+@@ -918,7 +935,7 @@ _END_GOOGLE_NAMESPACE_
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+-bool Symbolize(void *pc, char *out, size_t out_size) {
++bool Symbolize(void* pc, char* out, size_t out_size) {
+   return SymbolizeAndDemangle(pc, out, out_size);
+ }
+ 
+diff --git a/base/third_party/symbolize/symbolize.h b/base/third_party/symbolize/symbolize.h
+index 64ff0509ce57d..987569fdde67f 100644
+--- a/base/third_party/symbolize/symbolize.h
++++ b/base/third_party/symbolize/symbolize.h
+@@ -127,7 +127,7 @@ ATTRIBUTE_NOINLINE int OpenObjectFileContainingPcAndGetStartAddress(
+ 
+ _END_GOOGLE_NAMESPACE_
+ 
+-#endif  /* __ELF__ */
++#endif /* __ELF__ */
+ 
+ _START_GOOGLE_NAMESPACE_
+ 
+@@ -140,7 +140,7 @@ struct FileDescriptor {
+   int get() { return fd_; }
+ 
+  private:
+-  FileDescriptor(const FileDescriptor &);
++  FileDescriptor(const FileDescriptor&);
+   void operator=(const FileDescriptor&);
+ };
+ 
+diff --git a/base/third_party/symbolize/utilities.h b/base/third_party/symbolize/utilities.h
+index 8c61380fad159..bb206a8020315 100644
+--- a/base/third_party/symbolize/utilities.h
++++ b/base/third_party/symbolize/utilities.h
+@@ -35,13 +35,13 @@
+ #define UTILITIES_H__
+ 
+ #ifdef HAVE___ATTRIBUTE__
+-# define ATTRIBUTE_NOINLINE __attribute__ ((noinline))
+-# define HAVE_ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE __attribute__((noinline))
++#define HAVE_ATTRIBUTE_NOINLINE
+ #elif defined(GLOG_OS_WINDOWS)
+-# define ATTRIBUTE_NOINLINE __declspec(noinline)
+-# define HAVE_ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE __declspec(noinline)
++#define HAVE_ATTRIBUTE_NOINLINE
+ #else
+-# define ATTRIBUTE_NOINLINE
++#define ATTRIBUTE_NOINLINE
+ #endif
+ 
+ #endif  // UTILITIES_H__
diff --git a/build/config/siso/clang_linux.star b/build/config/siso/clang_linux.star
index d46a641b..b88af3b 100644
--- a/build/config/siso/clang_linux.star
+++ b/build/config/siso/clang_linux.star
@@ -56,7 +56,7 @@
     step_config["rules"].extend([
         {
             "name": "clang/cxx",
-            "action": "cxx",
+            "action": "(.*_)?cxx",
             "command_prefix": "../../third_party/llvm-build/Release+Asserts/bin/clang++ ",
             "inputs": [
                 "third_party/llvm-build/Release+Asserts/bin/clang++",
@@ -66,7 +66,7 @@
         },
         {
             "name": "clang/cc",
-            "action": "cc",
+            "action": "(.*_)?cc",
             "command_prefix": "../../third_party/llvm-build/Release+Asserts/bin/clang ",
             "inputs": [
                 "third_party/llvm-build/Release+Asserts/bin/clang",
@@ -76,7 +76,7 @@
         },
         {
             "name": "clang-coverage/cxx",
-            "action": "cxx",
+            "action": "(.*_)?cxx",
             "command_prefix": "\"python3\" ../../build/toolchain/clang_code_coverage_wrapper.py",
             "inputs": [
                 "build/toolchain/clang_code_coverage_wrapper.py",
@@ -88,7 +88,7 @@
         },
         {
             "name": "clang-coverage/cc",
-            "action": "cc",
+            "action": "(.*_)?cc",
             "command_prefix": "\"python3\" ../../build/toolchain/clang_code_coverage_wrapper.py",
             "inputs": [
                 "build/toolchain/clang_code_coverage_wrapper.py",
diff --git a/cc/metrics/average_lag_tracking_manager_unittest.cc b/cc/metrics/average_lag_tracking_manager_unittest.cc
index c374d8ce..7548c0f9 100644
--- a/cc/metrics/average_lag_tracking_manager_unittest.cc
+++ b/cc/metrics/average_lag_tracking_manager_unittest.cc
@@ -97,7 +97,7 @@
         ui::ET_GESTURE_SCROLL_UPDATE, ui::ScrollInputType::kTouchscreen,
         kScrollIsNotInertial, scroll_update_type, delta, event_time,
         arrived_in_browser_main_timestamp,
-        base::IdType64<class ui::LatencyInfo>(trace_id));
+        base::IdType64<class ui::LatencyInfo>(trace_id), base::TimeTicks());
   }
 
   AverageLagTrackingManager average_lag_tracking_manager_;
diff --git a/cc/metrics/compositor_frame_reporter_unittest.cc b/cc/metrics/compositor_frame_reporter_unittest.cc
index 7a3c45d..0d8d6a1 100644
--- a/cc/metrics/compositor_frame_reporter_unittest.cc
+++ b/cc/metrics/compositor_frame_reporter_unittest.cc
@@ -103,7 +103,7 @@
         if (stage_durations[i] >= 0) {
           AdvanceNowByUs(stage_durations[i]);
           metrics->SetDispatchStageTimestamp(
-              EventMetrics::DispatchStage(i + 1));
+              EventMetrics::DispatchStage(i + 2));
         }
       }
     }
@@ -112,9 +112,11 @@
 
   std::unique_ptr<EventMetrics> CreateEventMetrics(ui::EventType type) {
     const base::TimeTicks event_time = AdvanceNowByUs(3);
+    const base::TimeTicks arrived_in_browser_main_timestamp = AdvanceNowByUs(2);
     AdvanceNowByUs(3);
-    return SetupEventMetrics(
-        EventMetrics::CreateForTesting(type, event_time, &test_tick_clock_));
+    return SetupEventMetrics(EventMetrics::CreateForTesting(
+        type, event_time, arrived_in_browser_main_timestamp,
+        &test_tick_clock_));
   }
 
   // Creates EventMetrics with elements in stage_durations representing each
@@ -1584,13 +1586,15 @@
   // Test with no previous stage predictions.
   std::vector<base::TimeDelta> expected_predictions1(kNumDispatchStages,
                                                      base::Microseconds(-1));
-  IntToTimeDeltaVector(expected_predictions1,
-                       std::vector<int>{/*kArrivedInBrowserMain=*/300,
-                                        /*kArrivedInRendererCompositor=*/300,
-                                        /*kRendererCompositorStarted=*/300,
-                                        /*kRendererCompositorFinished=*/300,
-                                        /*kRendererMainStarted=*/300,
-                                        /*kRendererMainFinished=*/300});
+  IntToTimeDeltaVector(
+      expected_predictions1,
+      std::vector<int>{/*kScrollsBlockingTouchDispatchedToRenderer=*/-1,
+                       /*kArrivedInBrowserMain=*/300,
+                       /*kArrivedInRendererCompositor=*/300,
+                       /*kRendererCompositorStarted=*/300,
+                       /*kRendererCompositorFinished=*/300,
+                       /*kRendererMainStarted=*/300,
+                       /*kRendererMainFinished=*/300});
   base::TimeDelta expected_transition1 = base::Microseconds(300);
   base::TimeDelta expected_total1 = base::Microseconds(2400);
   CompositorFrameReporter::EventLatencyInfo actual_predictions1 =
@@ -1603,14 +1607,14 @@
   std::vector<base::TimeDelta> expected_predictions2(kNumDispatchStages,
                                                      base::Microseconds(-1));
   IntToTimeDeltaVector(expected_predictions2,
-                       std::vector<int>{262, 262, 300, 412, 225, 450});
+                       std::vector<int>{300, 262, 262, 300, 412, 225, 450});
   base::TimeDelta expected_transition2 = base::Microseconds(390);
-  base::TimeDelta expected_total2 = base::Microseconds(2601);
+  base::TimeDelta expected_total2 = base::Microseconds(2901);
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{250, 250, 300, 450, 200, 500});
+                       std::vector<int>{300, 250, 250, 300, 450, 200, 500});
   actual_predictions2.transition_duration = base::Microseconds(420);
   pipeline_reporter_->CalculateEventLatencyPrediction(
       actual_predictions2, kLatencyPredictionDeviationThreshold);
@@ -1619,14 +1623,14 @@
   std::vector<base::TimeDelta> expected_predictions3(kNumDispatchStages,
                                                      base::Microseconds(-1));
   IntToTimeDeltaVector(expected_predictions3,
-                       std::vector<int>{300, 375, 450, 300, 300, 300});
+                       std::vector<int>{300, 300, 375, 450, 300, 300, 300});
   base::TimeDelta expected_transition3 = base::Microseconds(270);
-  base::TimeDelta expected_total3 = base::Microseconds(2595);
+  base::TimeDelta expected_total3 = base::Microseconds(2895);
   CompositorFrameReporter::EventLatencyInfo actual_predictions3 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions3.dispatch_durations,
-                       std::vector<int>{-1, 400, 500, 300, -1, -1});
+                       std::vector<int>{300, -1, 400, 500, 300, -1, -1});
   actual_predictions3.transition_duration = base::Microseconds(260);
   pipeline_reporter_->CalculateEventLatencyPrediction(
       actual_predictions3, kLatencyPredictionDeviationThreshold);
@@ -1688,7 +1692,7 @@
   std::vector<base::TimeDelta> expected_predictions1(kNumDispatchStages,
                                                      base::Microseconds(-1));
   IntToTimeDeltaVector(expected_predictions1,
-                       std::vector<int>{200, 400, 600, 700, -1, -1});
+                       std::vector<int>{-1, 200, 400, 600, 700, -1, -1});
   base::TimeDelta expected_transition1 = base::Microseconds(470);
   base::TimeDelta expected_total1 = base::Microseconds(2670);
   CompositorFrameReporter::EventLatencyInfo actual_predictions1 =
@@ -1701,14 +1705,14 @@
   std::vector<base::TimeDelta> expected_predictions2(kNumDispatchStages,
                                                      base::Microseconds(-1));
   IntToTimeDeltaVector(expected_predictions2,
-                       std::vector<int>{125, 250, 375, 475, 200, 500});
+                       std::vector<int>{100, 125, 250, 375, 475, 200, 500});
   base::TimeDelta expected_transition2 = base::Microseconds(402);
-  base::TimeDelta expected_total2 = base::Microseconds(2627);
+  base::TimeDelta expected_total2 = base::Microseconds(2727);
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{100, 200, 300, 400, 200, 500});
+                       std::vector<int>{100, 100, 200, 300, 400, 200, 500});
   actual_predictions2.transition_duration = base::Microseconds(380);
   pipeline_reporter_->CalculateEventLatencyPrediction(
       actual_predictions2, kLatencyPredictionDeviationThreshold);
@@ -1717,14 +1721,14 @@
   std::vector<base::TimeDelta> expected_predictions3(kNumDispatchStages,
                                                      base::Microseconds(-1));
   IntToTimeDeltaVector(expected_predictions3,
-                       std::vector<int>{143, 400, 525, 745, -1, -1});
+                       std::vector<int>{125, 143, 400, 525, 745, -1, -1});
   base::TimeDelta expected_transition3 = base::Microseconds(492);
-  base::TimeDelta expected_total3 = base::Microseconds(2605);
+  base::TimeDelta expected_total3 = base::Microseconds(2730);
   CompositorFrameReporter::EventLatencyInfo actual_predictions3 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions3.dispatch_durations,
-                       std::vector<int>{125, 400, 500, 760, -1, -1});
+                       std::vector<int>{125, 125, 400, 500, 760, -1, -1});
   actual_predictions3.transition_duration = base::Microseconds(500);
   pipeline_reporter_->CalculateEventLatencyPrediction(
       actual_predictions3, kLatencyPredictionDeviationThreshold);
@@ -1796,13 +1800,15 @@
   // Test with no previous stage predictions.
   std::vector<base::TimeDelta> expected_dispatch1(kNumDispatchStages,
                                                   base::Microseconds(-1));
-  IntToTimeDeltaVector(expected_dispatch1,
-                       std::vector<int>{/*kArrivedInBrowserMain=*/300,
-                                        /*kArrivedInRendererCompositor=*/300,
-                                        /*kRendererCompositorStarted=*/300,
-                                        /*kRendererCompositorFinished=*/300,
-                                        /*kRendererMainStarted=*/300,
-                                        /*kRendererMainFinished=*/300});
+  IntToTimeDeltaVector(
+      expected_dispatch1,
+      std::vector<int>{/*kScrollsBlockingTouchDispatchedToRenderer=*/-1,
+                       /*kArrivedInBrowserMain=*/300,
+                       /*kArrivedInRendererCompositor=*/300,
+                       /*kRendererCompositorStarted=*/300,
+                       /*kRendererCompositorFinished=*/300,
+                       /*kRendererMainStarted=*/300,
+                       /*kRendererMainFinished=*/300});
   base::TimeDelta expected_transition1 = base::Microseconds(300);
   std::vector<base::TimeDelta> expected_compositor1(kNumOfCompositorStages,
                                                     base::Microseconds(-1));
@@ -1819,18 +1825,18 @@
   std::vector<base::TimeDelta> expected_dispatch2(kNumDispatchStages,
                                                   base::Microseconds(-1));
   IntToTimeDeltaVector(expected_dispatch2,
-                       std::vector<int>{262, 262, 300, 412, 225, 450});
+                       std::vector<int>{250, 262, 262, 300, 412, 225, 450});
   base::TimeDelta expected_transition2 = base::Microseconds(390);
   std::vector<base::TimeDelta> expected_compositor2(kNumOfCompositorStages,
                                                     base::Microseconds(-1));
   IntToTimeDeltaVector(expected_compositor2,
                        std::vector<int>{465, 500, 90, 720, 410, 742, 390});
-  base::TimeDelta expected_total2 = base::Microseconds(5618);
+  base::TimeDelta expected_total2 = base::Microseconds(5868);
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{250, 250, 300, 450, 200, 500});
+                       std::vector<int>{250, 250, 250, 300, 450, 200, 500});
   actual_predictions2.transition_duration = base::Microseconds(420);
   IntToTimeDeltaVector(actual_predictions2.compositor_durations,
                        std::vector<int>{520, 500, 90, 720, 410, 890, 420});
@@ -1841,18 +1847,18 @@
   std::vector<base::TimeDelta> expected_dispatch3(kNumDispatchStages,
                                                   base::Microseconds(-1));
   IntToTimeDeltaVector(expected_dispatch3,
-                       std::vector<int>{375, 375, 450, 300, 300, 300});
+                       std::vector<int>{400, 375, 375, 450, 300, 300, 300});
   base::TimeDelta expected_transition3 = base::Microseconds(270);
   std::vector<base::TimeDelta> expected_compositor3(kNumOfCompositorStages,
                                                     base::Microseconds(-1));
   IntToTimeDeltaVector(expected_compositor3,
                        std::vector<int>{300, 500, -1, -1, 410, 742, 390});
-  base::TimeDelta expected_total3 = base::Microseconds(4712);
+  base::TimeDelta expected_total3 = base::Microseconds(5112);
   CompositorFrameReporter::EventLatencyInfo actual_predictions3 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions3.dispatch_durations,
-                       std::vector<int>{400, 400, 500, 300, -1, -1});
+                       std::vector<int>{400, 400, 400, 500, 300, -1, -1});
   actual_predictions3.transition_duration = base::Microseconds(260);
   IntToTimeDeltaVector(actual_predictions3.compositor_durations,
                        std::vector<int>{-1, 500, -1, -1, 410, 890, 420});
@@ -1939,21 +1945,23 @@
   // Test with no previous stage predictions.
   std::vector<base::TimeDelta> expected_dispatch1(kNumDispatchStages,
                                                   base::Microseconds(-1));
-  IntToTimeDeltaVector(expected_dispatch1,
-                       std::vector<int>{/*kArrivedInBrowserMain=*/300,
-                                        /*kArrivedInRendererCompositor=*/300,
-                                        /*kRendererCompositorStarted=*/300,
-                                        /*kRendererCompositorFinished=*/300,
-                                        /*kRendererMainStarted=*/300,
-                                        /*kRendererMainFinished=*/300});
+  IntToTimeDeltaVector(
+      expected_dispatch1,
+      std::vector<int>{/*kScrollsBlockingTouchDispatchedToRenderer=*/-1,
+                       /*kArrivedInBrowserMain=*/300,
+                       /*kArrivedInRendererCompositor=*/300,
+                       /*kRendererCompositorStarted=*/300,
+                       /*kRendererCompositorFinished=*/300,
+                       /*kRendererMainStarted=*/300,
+                       /*kRenderePrMainFinished=*/300});
   base::TimeDelta expected_transition1 =
-      base::Microseconds(300) + kTouchEventTransition;
+      base::Microseconds(302) + kTouchEventTransition;
   std::vector<base::TimeDelta> expected_compositor1(kNumOfCompositorStages,
                                                     base::Microseconds(-1));
   IntToTimeDeltaVector(expected_compositor1,
                        std::vector<int>{300, -1, -1, -1, -1, 300, 300});
   base::TimeDelta expected_total1 =
-      base::Microseconds(3000) + kTouchEventTransition;
+      base::Microseconds(3002) + kTouchEventTransition;
   CompositorFrameReporter::EventLatencyInfo actual_predictions1 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
@@ -1964,18 +1972,18 @@
   std::vector<base::TimeDelta> expected_dispatch2(kNumDispatchStages,
                                                   base::Microseconds(-1));
   IntToTimeDeltaVector(expected_dispatch2,
-                       std::vector<int>{262, 262, 300, 412, 225, 450});
+                       std::vector<int>{250, 262, 262, 300, 412, 225, 450});
   base::TimeDelta expected_transition2 = base::Microseconds(393);
   std::vector<base::TimeDelta> expected_compositor2(kNumOfCompositorStages,
                                                     base::Microseconds(-1));
   IntToTimeDeltaVector(expected_compositor2,
                        std::vector<int>{465, 500, 90, 720, 410, 742, 390});
-  base::TimeDelta expected_total2 = base::Microseconds(5621);
+  base::TimeDelta expected_total2 = base::Microseconds(5871);
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{250, 250, 300, 450, 200, 500});
+                       std::vector<int>{250, 250, 250, 300, 450, 200, 500});
   actual_predictions2.transition_duration = base::Microseconds(420);
   IntToTimeDeltaVector(actual_predictions2.compositor_durations,
                        std::vector<int>{520, 500, 90, 720, 410, 890, 420});
@@ -2052,7 +2060,7 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(expected_predictions1.dispatch_durations,
-                       std::vector<int>{300, 300, 300, 300, 50000, 300});
+                       std::vector<int>{-1, 300, 300, 300, 300, 50000, 300});
   expected_predictions1.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(expected_predictions1.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
@@ -2075,17 +2083,17 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(expected_predictions2.dispatch_durations,
-                       std::vector<int>{300, 300, 300, 300, 12725, 300});
+                       std::vector<int>{300, 300, 300, 300, 300, 12725, 300});
   expected_predictions2.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(expected_predictions2.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
-  expected_predictions2.total_duration = base::Microseconds(65125);
+  expected_predictions2.total_duration = base::Microseconds(65425);
 
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{300, 300, 300, 300, 300, 300});
+                       std::vector<int>{300, 300, 300, 300, 300, 300, 300});
   actual_predictions2.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(actual_predictions2.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 50000, 300});
@@ -2103,17 +2111,17 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(expected_predictions3.dispatch_durations,
-                       std::vector<int>{300, 300, 300, 300, 12725, 300});
+                       std::vector<int>{300, 300, 300, 300, 300, 12725, 300});
   expected_predictions3.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(expected_predictions3.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 12725, 300});
-  expected_predictions3.total_duration = base::Microseconds(27850);
+  expected_predictions3.total_duration = base::Microseconds(28150);
 
   CompositorFrameReporter::EventLatencyInfo actual_predictions3 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions3.dispatch_durations,
-                       std::vector<int>{300, 300, 300, 300, 300, 300});
+                       std::vector<int>{300, 300, 300, 300, 300, 300, 300});
   actual_predictions3.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(actual_predictions3.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 300, 300});
@@ -2132,21 +2140,16 @@
               actual_predictions1.dispatch_durations[i]);
     EXPECT_EQ(expected_predictions2.dispatch_durations[i],
               actual_predictions2.dispatch_durations[i]);
-    ;
     EXPECT_EQ(expected_predictions3.dispatch_durations[i],
               actual_predictions3.dispatch_durations[i]);
-    ;
   }
   for (int i = 0; i < kNumOfCompositorStages; i++) {
     EXPECT_EQ(expected_predictions1.compositor_durations[i],
               actual_predictions1.compositor_durations[i]);
-    ;
     EXPECT_EQ(expected_predictions2.compositor_durations[i],
               actual_predictions2.compositor_durations[i]);
-    ;
     EXPECT_EQ(expected_predictions3.compositor_durations[i],
               actual_predictions3.compositor_durations[i]);
-    ;
   }
   EXPECT_EQ(expected_predictions1.transition_duration,
             actual_predictions1.transition_duration);
@@ -2217,7 +2220,7 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(expected_predictions1.dispatch_durations,
-                       std::vector<int>{10300, 262, -1, -1, 262, 42500});
+                       std::vector<int>{-1, 10300, 262, -1, -1, 262, 42500});
   expected_predictions1.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(expected_predictions1.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 15200, 300});
@@ -2227,7 +2230,7 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions1.dispatch_durations,
-                       std::vector<int>{400, 300, -1, -1, 300, 40000});
+                       std::vector<int>{-1, 400, 300, -1, -1, 300, 40000});
   actual_predictions1.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(actual_predictions1.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 3600, 300});
@@ -2248,18 +2251,18 @@
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(expected_predictions2.dispatch_durations,
-                       std::vector<int>{10225, 262, -1, -1, 262, 12725});
+                       std::vector<int>{300, 10225, 262, -1, -1, 262, 12725});
   expected_predictions2.transition_duration = base::Microseconds(300);
 
   IntToTimeDeltaVector(expected_predictions2.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 12725, 300});
-  expected_predictions2.total_duration = base::Microseconds(37099);
+  expected_predictions2.total_duration = base::Microseconds(37399);
 
   CompositorFrameReporter::EventLatencyInfo actual_predictions2 =
       CompositorFrameReporter::EventLatencyInfo(kNumDispatchStages,
                                                 kNumOfCompositorStages);
   IntToTimeDeltaVector(actual_predictions2.dispatch_durations,
-                       std::vector<int>{300, 300, -1, -1, 300, 300});
+                       std::vector<int>{300, 300, 300, -1, -1, 300, 300});
   actual_predictions2.transition_duration = base::Microseconds(300);
   IntToTimeDeltaVector(actual_predictions2.compositor_durations,
                        std::vector<int>{300, -1, -1, -1, -1, 300, 300});
diff --git a/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
index 654e1dc..142335b 100644
--- a/cc/metrics/compositor_frame_reporting_controller_unittest.cc
+++ b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
@@ -253,9 +253,11 @@
 
   std::unique_ptr<EventMetrics> CreateEventMetrics(ui::EventType type) {
     const base::TimeTicks event_time = AdvanceNowByMs(10);
+    const base::TimeTicks arrived_in_browser_main_timestamp = AdvanceNowByMs(3);
     AdvanceNowByMs(10);
-    return SetupEventMetrics(
-        EventMetrics::CreateForTesting(type, event_time, &test_tick_clock_));
+    return SetupEventMetrics(EventMetrics::CreateForTesting(
+        type, event_time, arrived_in_browser_main_timestamp,
+        &test_tick_clock_));
   }
 
   std::unique_ptr<EventMetrics> CreateScrollBeginEventMetrics(
diff --git a/cc/metrics/event_latency_tracing_recorder.cc b/cc/metrics/event_latency_tracing_recorder.cc
index 6877c9d6..7d8d11ac0 100644
--- a/cc/metrics/event_latency_tracing_recorder.cc
+++ b/cc/metrics/event_latency_tracing_recorder.cc
@@ -63,12 +63,26 @@
   switch (start_stage) {
     case EventMetrics::DispatchStage::kGenerated:
       switch (end_stage) {
+        case EventMetrics::DispatchStage::
+            kScrollsBlockingTouchDispatchedToRenderer:
         case EventMetrics::DispatchStage::kArrivedInBrowserMain:
           return "GenerationToBrowserMain";
         case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
           return "GenerationToRendererCompositor";
         default:
-          NOTREACHED();
+          NOTREACHED() << static_cast<int>(end_stage);
+          return "";
+      }
+    case EventMetrics::DispatchStage::kScrollsBlockingTouchDispatchedToRenderer:
+      switch (end_stage) {
+        case EventMetrics::DispatchStage::kArrivedInBrowserMain:
+          // This stage can only be in a Scroll EventLatency. It means a path of
+          // a corresponding blocking TouchMove from BrowserMain To Renderer To
+          // BrowserMain. Look at the corresponding TouchMove EventLatency for
+          // a more detailed breakdown of this stage.
+          return "TouchRendererHandlingToBrowserMain";
+        default:
+          NOTREACHED() << static_cast<int>(end_stage);
           return "";
       }
     case EventMetrics::DispatchStage::kArrivedInBrowserMain:
@@ -82,7 +96,7 @@
         case EventMetrics::DispatchStage::kRendererMainStarted:
           return "RendererCompositorToMain";
         default:
-          NOTREACHED();
+          NOTREACHED() << static_cast<int>(end_stage);
           return "";
       }
     case EventMetrics::DispatchStage::kRendererCompositorStarted:
diff --git a/cc/metrics/event_metrics.cc b/cc/metrics/event_metrics.cc
index 14d56dd6..882896b 100644
--- a/cc/metrics/event_metrics.cc
+++ b/cc/metrics/event_metrics.cc
@@ -213,6 +213,14 @@
 // static
 std::unique_ptr<EventMetrics> EventMetrics::Create(ui::EventType type,
                                                    base::TimeTicks timestamp) {
+  return Create(type, timestamp, base::TimeTicks());
+}
+
+// static
+std::unique_ptr<EventMetrics> EventMetrics::Create(
+    ui::EventType type,
+    base::TimeTicks timestamp,
+    base::TimeTicks arrived_in_browser_main_timestamp) {
   // TODO(crbug.com/1157090): We expect that `timestamp` is not null, but there
   // seems to be some tests that are emitting events with null timestamp. We
   // should investigate and try to fix those cases and add a `DCHECK` here to
@@ -221,7 +229,8 @@
   DCHECK(!IsGestureScroll(type) && !IsGesturePinch(type));
 
   std::unique_ptr<EventMetrics> metrics =
-      CreateInternal(type, timestamp, base::DefaultTickClock::GetInstance());
+      CreateInternal(type, timestamp, arrived_in_browser_main_timestamp,
+                     base::DefaultTickClock::GetInstance());
   if (!metrics)
     return nullptr;
 
@@ -234,11 +243,12 @@
 std::unique_ptr<EventMetrics> EventMetrics::CreateForTesting(
     ui::EventType type,
     base::TimeTicks timestamp,
+    base::TimeTicks arrived_in_browser_main_timestamp,
     const base::TickClock* tick_clock) {
   DCHECK(!timestamp.is_null());
 
   std::unique_ptr<EventMetrics> metrics =
-      CreateInternal(type, timestamp, tick_clock);
+      CreateInternal(type, timestamp, base::TimeTicks(), tick_clock);
   if (!metrics)
     return nullptr;
 
@@ -260,8 +270,8 @@
   if (!existing)
     return nullptr;
 
-  std::unique_ptr<EventMetrics> metrics =
-      CreateInternal(type, base::TimeTicks(), existing->tick_clock_);
+  std::unique_ptr<EventMetrics> metrics = CreateInternal(
+      type, base::TimeTicks(), base::TimeTicks(), existing->tick_clock_);
   if (!metrics)
     return nullptr;
 
@@ -276,14 +286,16 @@
 std::unique_ptr<EventMetrics> EventMetrics::CreateInternal(
     ui::EventType type,
     base::TimeTicks timestamp,
+    base::TimeTicks arrived_in_browser_main_timestamp,
     const base::TickClock* tick_clock) {
   absl::optional<EventType> interesting_type =
       ToInterestingEventType(type, /*scroll_is_inertial=*/absl::nullopt,
                              /*scroll_update_type=*/absl::nullopt);
   if (!interesting_type)
     return nullptr;
-  return base::WrapUnique(
-      new EventMetrics(*interesting_type, timestamp, tick_clock));
+  return base::WrapUnique(new EventMetrics(*interesting_type, timestamp,
+                                           arrived_in_browser_main_timestamp,
+                                           tick_clock));
 }
 
 EventMetrics::EventMetrics(EventType type,
@@ -343,6 +355,13 @@
       tick_clock_->NowTicks();
 }
 
+void EventMetrics::SetDispatchStageTimestamp(DispatchStage stage,
+                                             base::TimeTicks timestamp) {
+  DCHECK(dispatch_stage_timestamps_[static_cast<size_t>(stage)].is_null());
+
+  dispatch_stage_timestamps_[static_cast<size_t>(stage)] = timestamp;
+}
+
 base::TimeTicks EventMetrics::GetDispatchStageTimestamp(
     DispatchStage stage) const {
   return dispatch_stage_timestamps_[static_cast<size_t>(stage)];
@@ -405,7 +424,8 @@
     ui::ScrollInputType input_type,
     bool is_inertial,
     base::TimeTicks timestamp,
-    base::TimeTicks arrived_in_browser_main_timestamp) {
+    base::TimeTicks arrived_in_browser_main_timestamp,
+    base::TimeTicks blocking_touch_dispatched_to_renderer) {
   // TODO(crbug.com/1157090): We expect that `timestamp` is not null, but there
   // seems to be some tests that are emitting events with null timestamp.  We
   // should investigate and try to fix those cases and add a `DCHECK` here to
@@ -421,6 +441,9 @@
 
   metrics->SetDispatchStageTimestamp(
       DispatchStage::kArrivedInRendererCompositor);
+  metrics->SetDispatchStageTimestamp(
+      DispatchStage::kScrollsBlockingTouchDispatchedToRenderer,
+      blocking_touch_dispatched_to_renderer);
   return metrics;
 }
 
@@ -430,7 +453,9 @@
     ui::ScrollInputType input_type,
     bool is_inertial,
     base::TimeTicks timestamp) {
-  return Create(type, input_type, is_inertial, timestamp, base::TimeTicks());
+  return Create(type, input_type, is_inertial, timestamp,
+                /*arrived_in_browser_main_timestamp=*/base::TimeTicks(),
+                /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks());
 }
 
 // static
@@ -544,7 +569,8 @@
     float delta,
     base::TimeTicks timestamp,
     base::TimeTicks arrived_in_browser_main_timestamp,
-    TraceId trace_id) {
+    TraceId trace_id,
+    base::TimeTicks blocking_touch_dispatched_to_renderer) {
   // TODO(crbug.com/1157090): We expect that `timestamp` is not null, but there
   // seems to be some tests that are emitting events with null timestamp. We
   // should investigate and try to fix those cases and add a `DCHECK` here to
@@ -561,6 +587,9 @@
 
   metrics->SetDispatchStageTimestamp(
       DispatchStage::kArrivedInRendererCompositor);
+  metrics->SetDispatchStageTimestamp(
+      DispatchStage::kScrollsBlockingTouchDispatchedToRenderer,
+      blocking_touch_dispatched_to_renderer);
   return metrics;
 }
 
@@ -573,8 +602,10 @@
                                            float delta,
                                            base::TimeTicks timestamp,
                                            TraceId trace_id) {
-  return Create(type, input_type, is_inertial, scroll_update_type, delta,
-                timestamp, base::TimeTicks(), trace_id);
+  return Create(
+      type, input_type, is_inertial, scroll_update_type, delta, timestamp,
+      /*arrived_in_browser_main_timestamp=*/base::TimeTicks(), trace_id,
+      /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks());
 }
 
 // static
diff --git a/cc/metrics/event_metrics.h b/cc/metrics/event_metrics.h
index 1b1d68b..480fe35 100644
--- a/cc/metrics/event_metrics.h
+++ b/cc/metrics/event_metrics.h
@@ -68,6 +68,11 @@
   // Stages of event dispatch in different processes/threads.
   enum class DispatchStage {
     kGenerated,
+    // 'kScrollsBlockingTouchDispatchedToRenderer' is used by Scroll events to
+    // understand when a corresponding TouchMove event arrived in the Browser
+    // Main. If the related TouchMove wasn't blocking, this stage field is not
+    // set.
+    kScrollsBlockingTouchDispatchedToRenderer,
     kArrivedInBrowserMain,
     kArrivedInRendererCompositor,
     kRendererCompositorStarted,
@@ -77,16 +82,22 @@
     kMaxValue = kRendererMainFinished,
   };
 
+  static std::unique_ptr<EventMetrics> Create(ui::EventType type,
+                                              base::TimeTicks timestamp);
+
   // Returns a new instance if the event is of a type we are interested in.
   // Otherwise, returns `nullptr`. For scroll and pinch events, use the
   // appropriate subcalss instead.
-  static std::unique_ptr<EventMetrics> Create(ui::EventType type,
-                                              base::TimeTicks timestamp);
+  static std::unique_ptr<EventMetrics> Create(
+      ui::EventType type,
+      base::TimeTicks timestamp,
+      base::TimeTicks arrived_in_browser_main_timestamp);
 
   // Similar to `Create()` with an extra `base::TickClock` to use in tests.
   static std::unique_ptr<EventMetrics> CreateForTesting(
       ui::EventType type,
       base::TimeTicks timestamp,
+      base::TimeTicks arrived_in_browser_main_timestamp,
       const base::TickClock* tick_clock);
 
   // Used to create an instance for an event generated based on an existing
@@ -182,6 +193,9 @@
   void CopyTimestampsFrom(const EventMetrics& other,
                           DispatchStage last_dispatch_stage);
 
+  void SetDispatchStageTimestamp(DispatchStage stage,
+                                 base::TimeTicks timestamp);
+
  private:
   friend class ScrollEventMetrics;
   friend class ScrollUpdateEventMetrics;
@@ -189,6 +203,7 @@
   static std::unique_ptr<EventMetrics> CreateInternal(
       ui::EventType type,
       base::TimeTicks timestamp,
+      base::TimeTicks arrived_in_browser_main_timestamp,
       const base::TickClock* tick_clock);
 
   EventType type_;
@@ -232,6 +247,8 @@
   // Returns a new instance if the event is of a type we are interested in.
   // Otherwise, returns `nullptr`. Should only be used for scroll events other
   // than scroll-update.
+  // The |blocking_touch_dispatched_to_renderer| must be not null only for
+  // scrolls which corresponding TouchMove was blocking.
   //
   // TODO(b/224960731): Fix tests and stop supporting the case when
   // `arrived_in_browser_main_timestamp` is null.
@@ -240,11 +257,13 @@
       ui::ScrollInputType input_type,
       bool is_inertial,
       base::TimeTicks timestamp,
-      base::TimeTicks arrived_in_browser_main_timestamp);
+      base::TimeTicks arrived_in_browser_main_timestamp,
+      base::TimeTicks blocking_touch_dispatched_to_renderer);
 
   // Prefer to use `Create()` above. This method is used only by the Browser
   // process which have own breakdowns.
-  // Similar to `Create()` above but doesn't set kArrivedInBrowserMain.
+  // Similar to `Create()` above but doesn't set kArrivedInBrowserMain and
+  // kScrollsBlockingTouchDispatchedToRenderer.
   static std::unique_ptr<ScrollEventMetrics> CreateForBrowser(
       ui::EventType type,
       ui::ScrollInputType input_type,
@@ -319,6 +338,8 @@
 
   // Returns a new instance if the event is of a type we are interested in.
   // Otherwise, returns `nullptr`. Should only be used for scroll-update events.
+  // The |blocking_touch_dispatched_to_renderer| must be not null only for
+  // scrolls which corresponding TouchMove was blocking.
   //
   // TODO(b/224960731): Fix tests and stop supporting the case when
   // `arrived_in_browser_main_timestamp` is null.
@@ -330,11 +351,13 @@
       float delta,
       base::TimeTicks timestamp,
       base::TimeTicks arrived_in_browser_main_timestamp,
-      TraceId trace_id);
+      TraceId trace_id,
+      base::TimeTicks blocking_touch_dispatched_to_renderer);
 
   // Prefer to use `Create()` above. This method is used only by the Browser
   // process which have own breakdowns.
-  // Similar to `Create()` above but doesn't set kArrivedInBrowserMain.
+  // Similar to `Create()` above but doesn't set kArrivedInBrowserMain and
+  // kScrollsBlockingTouchDispatchedToRenderer.
   static std::unique_ptr<ScrollUpdateEventMetrics> CreateForBrowser(
       ui::EventType type,
       ui::ScrollInputType input_type,
diff --git a/cc/metrics/event_metrics_unittest.cc b/cc/metrics/event_metrics_unittest.cc
index 758e9c7..81e88c7 100644
--- a/cc/metrics/event_metrics_unittest.cc
+++ b/cc/metrics/event_metrics_unittest.cc
@@ -25,6 +25,7 @@
 TEST_F(EventMetricsTest, ScrollBeginCreateWithNullBeginRwhTime) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp;
   base::TimeTicks arrived_in_browser_main_timestamp;
   base::TimeTicks now = base::TimeTicks::Now();
 
@@ -32,7 +33,8 @@
   std::unique_ptr<ScrollEventMetrics> scroll_event_metric =
       ScrollEventMetrics::Create(
           ui::ET_GESTURE_SCROLL_BEGIN, ui::ScrollInputType::kTouchscreen,
-          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp);
+          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp,
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Assert
   EXPECT_EQ(event_time, scroll_event_metric->GetDispatchStageTimestamp(
@@ -43,6 +45,11 @@
   // not set
   EXPECT_TRUE(scroll_event_metric
                   ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::
+                          kScrollsBlockingTouchDispatchedToRenderer)
+                  .is_null());
+  EXPECT_TRUE(scroll_event_metric
+                  ->GetDispatchStageTimestamp(
                       EventMetrics::DispatchStage::kArrivedInBrowserMain)
                   .is_null());
   EXPECT_TRUE(scroll_event_metric
@@ -66,6 +73,8 @@
 TEST_F(EventMetricsTest, ScrollBeginCreate) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(70);
   base::TimeTicks arrived_in_browser_main_timestamp =
       base::TimeTicks::Now() - base::Microseconds(50);
   base::TimeTicks now = base::TimeTicks::Now();
@@ -74,11 +83,16 @@
   std::unique_ptr<ScrollEventMetrics> scroll_event_metric =
       ScrollEventMetrics::Create(
           ui::ET_GESTURE_SCROLL_BEGIN, ui::ScrollInputType::kTouchscreen,
-          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp);
+          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp,
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Assert
   EXPECT_EQ(event_time, scroll_event_metric->GetDispatchStageTimestamp(
                             EventMetrics::DispatchStage::kGenerated));
+  EXPECT_EQ(blocking_touch_dispatched_to_renderer_timestamp,
+            scroll_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer));
   EXPECT_EQ(arrived_in_browser_main_timestamp,
             scroll_event_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain));
@@ -107,12 +121,15 @@
 TEST_F(EventMetricsTest, ScrollBeginCreateFromExisting) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(70);
   base::TimeTicks arrived_in_browser_main_timestamp =
       base::TimeTicks::Now() - base::Microseconds(50);
   std::unique_ptr<ScrollEventMetrics> scroll_metric =
       ScrollEventMetrics::Create(
           ui::ET_GESTURE_SCROLL_BEGIN, ui::ScrollInputType::kTouchscreen,
-          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp);
+          /*is_inertial=*/false, event_time, arrived_in_browser_main_timestamp,
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Act
   std::unique_ptr<ScrollEventMetrics> copy_scroll_metric =
@@ -128,6 +145,12 @@
             copy_scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kGenerated));
   EXPECT_EQ(scroll_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer),
+            copy_scroll_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer));
+  EXPECT_EQ(scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain),
             copy_scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain));
@@ -161,6 +184,7 @@
 TEST_F(EventMetricsTest, ScrollUpdateCreateWithNullBeginRwhTime) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp;
   base::TimeTicks arrived_in_browser_main_timestamp;
   base::TimeTicks now = base::TimeTicks::Now();
   TraceId trace_id(123);
@@ -171,7 +195,8 @@
           ui::ET_GESTURE_SCROLL_UPDATE, ui::ScrollInputType::kTouchscreen,
           /*is_inertial=*/false,
           ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, /*delta=*/0.4,
-          event_time, arrived_in_browser_main_timestamp, trace_id);
+          event_time, arrived_in_browser_main_timestamp, trace_id,
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Assert
   EXPECT_EQ(trace_id, scroll_event_metric->trace_id());
@@ -183,6 +208,11 @@
   // not set
   EXPECT_TRUE(scroll_event_metric
                   ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::
+                          kScrollsBlockingTouchDispatchedToRenderer)
+                  .is_null());
+  EXPECT_TRUE(scroll_event_metric
+                  ->GetDispatchStageTimestamp(
                       EventMetrics::DispatchStage::kArrivedInBrowserMain)
                   .is_null());
   EXPECT_TRUE(scroll_event_metric
@@ -206,6 +236,8 @@
 TEST_F(EventMetricsTest, ScrollUpdateCreate) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(70);
   base::TimeTicks arrived_in_browser_main_timestamp =
       base::TimeTicks::Now() - base::Microseconds(50);
   base::TimeTicks now = base::TimeTicks::Now();
@@ -217,12 +249,17 @@
           ui::ET_GESTURE_SCROLL_UPDATE, ui::ScrollInputType::kTouchscreen,
           /*is_inertial=*/false,
           ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, /*delta=*/0.4,
-          event_time, arrived_in_browser_main_timestamp, TraceId(trace_id));
+          event_time, arrived_in_browser_main_timestamp, TraceId(trace_id),
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Assert
   EXPECT_EQ(trace_id, scroll_event_metric->trace_id());
   EXPECT_EQ(event_time, scroll_event_metric->GetDispatchStageTimestamp(
                             EventMetrics::DispatchStage::kGenerated));
+  EXPECT_EQ(blocking_touch_dispatched_to_renderer_timestamp,
+            scroll_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer));
   EXPECT_EQ(arrived_in_browser_main_timestamp,
             scroll_event_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain));
@@ -251,6 +288,8 @@
 TEST_F(EventMetricsTest, ScrollUpdateCreateFromExisting) {
   // Arrange
   base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(70);
   base::TimeTicks arrived_in_browser_main_timestamp =
       base::TimeTicks::Now() - base::Microseconds(50);
   TraceId trace_id(123);
@@ -259,7 +298,8 @@
           ui::ET_GESTURE_SCROLL_UPDATE, ui::ScrollInputType::kTouchscreen,
           /*is_inertial=*/false,
           ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, /*delta=*/0.4,
-          event_time, arrived_in_browser_main_timestamp, trace_id);
+          event_time, arrived_in_browser_main_timestamp, trace_id,
+          blocking_touch_dispatched_to_renderer_timestamp);
 
   // Act
   std::unique_ptr<ScrollUpdateEventMetrics> copy_scroll_metric =
@@ -277,6 +317,12 @@
             copy_scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kGenerated));
   EXPECT_EQ(scroll_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer),
+            copy_scroll_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::
+                    kScrollsBlockingTouchDispatchedToRenderer));
+  EXPECT_EQ(scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain),
             copy_scroll_metric->GetDispatchStageTimestamp(
                 EventMetrics::DispatchStage::kArrivedInBrowserMain));
@@ -307,5 +353,95 @@
                 EventMetrics::DispatchStage::kRendererMainFinished));
 }
 
+TEST_F(EventMetricsTest, Create) {
+  // Arrange
+  base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks arrived_in_browser_main_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(50);
+  base::TimeTicks now = base::TimeTicks::Now();
+
+  // Act
+  std::unique_ptr<EventMetrics> event_metric = EventMetrics::Create(
+      ui::ET_TOUCH_MOVED, event_time, arrived_in_browser_main_timestamp);
+
+  // Assert
+  EXPECT_EQ(event_time, event_metric->GetDispatchStageTimestamp(
+                            EventMetrics::DispatchStage::kGenerated));
+  EXPECT_EQ(arrived_in_browser_main_timestamp,
+            event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInBrowserMain));
+  EXPECT_LE(now,
+            event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInRendererCompositor));
+  // not set
+  EXPECT_TRUE(event_metric
+                  ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::kRendererCompositorStarted)
+                  .is_null());
+  EXPECT_TRUE(event_metric
+                  ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::kRendererCompositorFinished)
+                  .is_null());
+  EXPECT_TRUE(event_metric
+                  ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::kRendererMainStarted)
+                  .is_null());
+  EXPECT_TRUE(event_metric
+                  ->GetDispatchStageTimestamp(
+                      EventMetrics::DispatchStage::kRendererMainFinished)
+                  .is_null());
+}
+
+TEST_F(EventMetricsTest, CreateFromExisting) {
+  // Arrange
+  base::TimeTicks event_time = base::TimeTicks::Now() - base::Microseconds(100);
+  base::TimeTicks arrived_in_browser_main_timestamp =
+      base::TimeTicks::Now() - base::Microseconds(50);
+  std::unique_ptr<EventMetrics> event_metric = EventMetrics::Create(
+      ui::ET_TOUCH_MOVED, event_time, arrived_in_browser_main_timestamp);
+
+  // Act
+  std::unique_ptr<EventMetrics> copy_event_metric =
+      EventMetrics::CreateFromExisting(
+          ui::ET_TOUCH_MOVED,
+          EventMetrics::DispatchStage::kRendererMainFinished,
+          event_metric.get());
+
+  // Assert
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kGenerated),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kGenerated));
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInBrowserMain),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInBrowserMain));
+
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInRendererCompositor),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kArrivedInRendererCompositor));
+
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererCompositorStarted),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererCompositorStarted));
+
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererCompositorFinished),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererCompositorFinished));
+
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererMainStarted),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererMainStarted));
+
+  EXPECT_EQ(event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererMainFinished),
+            copy_event_metric->GetDispatchStageTimestamp(
+                EventMetrics::DispatchStage::kRendererMainFinished));
+}
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/metrics/events_metrics_manager_unittest.cc b/cc/metrics/events_metrics_manager_unittest.cc
index 2692020..713705c 100644
--- a/cc/metrics/events_metrics_manager_unittest.cc
+++ b/cc/metrics/events_metrics_manager_unittest.cc
@@ -54,8 +54,12 @@
   std::unique_ptr<EventMetrics> CreateEventMetrics(ui::EventType type) {
     test_tick_clock_.Advance(base::Microseconds(10));
     base::TimeTicks event_time = test_tick_clock_.NowTicks();
+    test_tick_clock_.Advance(base::Microseconds(5));
+    base::TimeTicks arrived_in_browser_main_timestamp =
+        test_tick_clock_.NowTicks();
     test_tick_clock_.Advance(base::Microseconds(10));
-    return EventMetrics::CreateForTesting(type, event_time, &test_tick_clock_);
+    return EventMetrics::CreateForTesting(
+        type, event_time, arrived_in_browser_main_timestamp, &test_tick_clock_);
   }
 
   EventsMetricsManager manager_;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 4b3b837..1c4fb260 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -14370,7 +14370,8 @@
                : ScrollUpdateEventMetrics::ScrollUpdateType::kContinued,
         /*delta=*/10.0f, base::TimeTicks::Now(),
         base::TimeTicks::Now() + base::Milliseconds(1),
-        /*trace_id*/ base::IdType64<class ui::LatencyInfo>(123)));
+        /*trace_id*/ base::IdType64<class ui::LatencyInfo>(123),
+        base::TimeTicks()));
     host_impl_->active_tree()->AppendEventsMetricsFromMainThread(
         std::move(events_metrics));
 
diff --git a/cc/trees/ukm_manager.cc b/cc/trees/ukm_manager.cc
index d5c2704..fa6f393 100644
--- a/cc/trees/ukm_manager.cc
+++ b/cc/trees/ukm_manager.cc
@@ -206,6 +206,8 @@
       switch (dispatch_stage) {
         case EventMetrics::DispatchStage::kGenerated:
           switch (end_stage) {
+            case EventMetrics::DispatchStage::
+                kScrollsBlockingTouchDispatchedToRenderer:
             case EventMetrics::DispatchStage::kArrivedInBrowserMain:
               // Will build the `GenerationToRendererCompositor` metric on the
               // `kArrivedInBrowserMain` stage.
@@ -218,6 +220,9 @@
               break;
           }
           break;
+        case EventMetrics::DispatchStage::
+            kScrollsBlockingTouchDispatchedToRenderer:
+          break;
         case EventMetrics::DispatchStage::kArrivedInBrowserMain:
           DCHECK_EQ(end_stage,
                     EventMetrics::DispatchStage::kArrivedInRendererCompositor);
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/PasswordGenerationIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/PasswordGenerationIntegrationTest.java
index fc92d45..2d55ec1b 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/PasswordGenerationIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/PasswordGenerationIntegrationTest.java
@@ -242,9 +242,7 @@
     }
 
     private void focusField(String node) throws TimeoutException, InterruptedException {
-        DOMUtils.focusNode(mHelper.getWebContents(), node);
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> { mHelper.getWebContents().scrollFocusedEditableNodeIntoView(); });
+        DOMUtils.clickNode(mHelper.getWebContents(), node);
     }
 
     private void clickNode(String node) throws InterruptedException, TimeoutException {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
index 7e0ba50..0ca6d610 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncErrorNotifier.java
@@ -28,7 +28,6 @@
 import org.chromium.components.browser_ui.notifications.NotificationMetadata;
 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
 import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
-import org.chromium.components.sync.PassphraseType;
 import org.chromium.components.sync.TrustedVaultUserActionTriggerForUMA;
 
 import java.lang.annotation.Retention;
@@ -160,39 +159,30 @@
     }
 
     private @NotificationState int computeGoalNotificationState() {
-        // TODO(crbug.com/1402252): Remove returns with else branches (left them for a better diff).
         if (!mSyncService.isSyncFeatureEnabled()) {
+            // Error notifications are only currently shown to syncing users, even though passphrase
+            // and trusted vault key errors still apply to signed-in users.
             return NotificationState.HIDDEN;
-        } else if (mSyncService.isEngineInitialized()
-                && mSyncService.isPassphraseRequiredForPreferredDataTypes()) {
-            assert (!mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes());
+        }
 
-            if (mSyncService.isPassphrasePromptMutedForCurrentProductVersion()) {
-                return NotificationState.HIDDEN;
-            }
+        if (!mSyncService.isEngineInitialized()) {
+            // The notifications expose encryption errors and those can only be detected once the
+            // engine is up. In the meantime, don't show anything.
+            return NotificationState.HIDDEN;
+        }
 
-            switch (mSyncService.getPassphraseType()) {
-                case PassphraseType.IMPLICIT_PASSPHRASE:
-                case PassphraseType.FROZEN_IMPLICIT_PASSPHRASE:
-                case PassphraseType.CUSTOM_PASSPHRASE:
-                    return NotificationState.REQUIRE_PASSPHRASE;
-                case PassphraseType.TRUSTED_VAULT_PASSPHRASE:
-                    assert false : "Passphrase cannot be required with trusted vault passphrase";
-                    return NotificationState.HIDDEN;
-                case PassphraseType.KEYSTORE_PASSPHRASE:
-                    return NotificationState.HIDDEN;
-                default:
-                    assert false : "Unknown passphrase type";
-                    return NotificationState.HIDDEN;
-            }
-        } else if (mSyncService.isEngineInitialized()
-                && mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()) {
+        if (mSyncService.isPassphraseRequiredForPreferredDataTypes()
+                && !mSyncService.isPassphrasePromptMutedForCurrentProductVersion()) {
+            return NotificationState.REQUIRE_PASSPHRASE;
+        }
+
+        if (mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()) {
             return mSyncService.isEncryptEverythingEnabled()
                     ? NotificationState.REQUIRE_TRUSTED_VAULT_KEY_FOR_EVERYTHING
                     : NotificationState.REQUIRE_TRUSTED_VAULT_KEY_FOR_PASSWORDS;
-        } else {
-            return NotificationState.HIDDEN;
         }
+
+        return NotificationState.HIDDEN;
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
index ace044eb..8e9e4b1 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/TouchToFillCreditCardTest.java
@@ -31,7 +31,6 @@
 
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
@@ -113,7 +112,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1433074")
     public void testSelectingLocalCard() throws TimeoutException {
         // Focus the field to bring up the touch to fill for credit cards.
         DOMUtils.clickNode(mWebContents, CREDIT_CARD_NUMBER_FIELD_ID);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/sync/SyncErrorNotifierTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/sync/SyncErrorNotifierTest.java
index e127d8f..4aa0c99 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/sync/SyncErrorNotifierTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/sync/SyncErrorNotifierTest.java
@@ -34,7 +34,6 @@
 import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
 import org.chromium.components.browser_ui.notifications.NotificationWrapper;
 import org.chromium.components.signin.base.CoreAccountInfo;
-import org.chromium.components.sync.PassphraseType;
 
 /** Unit tests for {@link SyncErrorNotifier}. */
 @RunWith(BaseRobolectricTestRunner.class)
@@ -64,7 +63,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(false);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(false);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(false);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.IMPLICIT_PASSPHRASE);
 
         SyncErrorNotifier notifier =
                 new SyncErrorNotifier(mNotificationManagerProxy, mSyncService, mTrustedVaultClient);
@@ -85,7 +83,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(true);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(false);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(false);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.CUSTOM_PASSPHRASE);
 
         SyncErrorNotifier notifier =
                 new SyncErrorNotifier(mNotificationManagerProxy, mSyncService, mTrustedVaultClient);
@@ -125,7 +122,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(true);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(true);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(false);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.CUSTOM_PASSPHRASE);
 
         SyncErrorNotifier notifier =
                 new SyncErrorNotifier(mNotificationManagerProxy, mSyncService, mTrustedVaultClient);
@@ -146,7 +142,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(false);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(false);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(true);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.TRUSTED_VAULT_PASSPHRASE);
         Promise<PendingIntent> intentPromise = new Promise<>();
         when(mTrustedVaultClient.createKeyRetrievalIntent(any())).thenReturn(intentPromise);
 
@@ -204,7 +199,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(false);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(false);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(true);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.TRUSTED_VAULT_PASSPHRASE);
         when(mTrustedVaultClient.createKeyRetrievalIntent(any()))
                 .thenReturn(Promise.fulfilled(null));
 
@@ -234,7 +228,6 @@
         when(mSyncService.isPassphraseRequiredForPreferredDataTypes()).thenReturn(false);
         when(mSyncService.isPassphrasePromptMutedForCurrentProductVersion()).thenReturn(false);
         when(mSyncService.isTrustedVaultKeyRequiredForPreferredDataTypes()).thenReturn(true);
-        when(mSyncService.getPassphraseType()).thenReturn(PassphraseType.TRUSTED_VAULT_PASSPHRASE);
         when(mTrustedVaultClient.createKeyRetrievalIntent(any())).thenReturn(Promise.rejected());
 
         SyncErrorNotifier notifier =
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e7c2d411..9b7d098 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -5002,7 +5002,7 @@
         </message>
         <if expr="reven">
           <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO" desc="Permission string for accessing information of attached devices.">
-            Read attached device information and data
+            Read attached devices information and data
           </message>
           <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS" desc="Permission string for chrome.os.diagnostcs API.">
             Run ChromeOS Flex diagnostic tests
@@ -5022,7 +5022,7 @@
         </if>
         <if expr="not reven">
           <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO" desc="Permission string for accessing information of attached devices.">
-            Read attached device information and data
+            Read attached devices information and data
           </message>
           <message name="IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_DIAGNOSTICS" desc="Permission string for chrome.os.diagnostcs API.">
             Run ChromeOS diagnostic tests
diff --git a/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO.png.sha1 b/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO.png.sha1
index 37889de1..d31282b8 100644
--- a/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_EXTENSION_PROMPT_WARNING_CHROMEOS_ATTACHED_DEVICE_INFO.png.sha1
@@ -1 +1 @@
-2e89dcf6650d0e271a0e0ca43104eca5cf4d7719
\ No newline at end of file
+66d6cbbd49303428ff5973f3c1f47477aedb0581
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 75d8d34a6..0eab143 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -3767,6 +3767,36 @@
   <message name="IDS_SETTINGS_INTERNET_PASSPOINT_PROVIDER" desc="Label of the link to the Passpoint provider details page.">
     Passpoint provider
   </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_REMOVE_SUBSCRIPTION" desc="Label of the button used to remove a subscription from the Passpoint detail page.">
+    Remove
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_HEADLINE" desc="Headline text describing how Passpoint subscription is synced with the user account.">
+    Subscription is installed on this device only and not synced with other devices under your account. <ph name="LINK_BEGIN">&lt;a&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_SUBSCRIPTION_EXPIRATION" desc="Label of the subscription expiration date.">
+    Expiry date
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_SOURCE" desc="Label of the source that installed the Passpoint subscription.">
+    Passpoint provider
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_TRUSTED_CA" desc="Label of the subscription trusted certificate authority used to authenticate the server.">
+    Trusted CA
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_SYSTEM_CA" desc="Text displayed when the subscription relies on system CA certificates for server authentication.">
+    System CAs
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS" desc="Label for the list of domains included in the Passpoint subscription.">
+    Domains
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS_A11Y_LABEL" desc="Label for the button that toggles showing the Passpoint domains. Only visible by screen reader software.">
+    Show domains
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_TITLE" desc="Title of the dialog displayed when the remove button is clicked on Passpoint details page.">
+    Remove subscription from device
+  </message>
+  <message name="IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_DESCRIPTION" desc="Text of the dialog displayed when the remove button is clicked on Passpoint details page.">
+    <ph name="SUBSCRIPTION_NAME">$1<ex>My Passpoint Provider</ex></ph> will be removed from this device only. To make changes to your subscription, contact the subscription provider. <ph name="LINK_BEGIN">&lt;a&gt;</ph>Learn more<ph name="LINK_END">&lt;/a&gt;</ph>
+  </message>
 
   <!-- Users Page (OS Settings) -->
   <message name="IDS_SETTINGS_USERS_MODIFIED_BY_OWNER_LABEL" desc="Label saying settings may only be modified by the device owner.">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS_A11Y_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS_A11Y_LABEL.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS_A11Y_LABEL.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_HEADLINE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_HEADLINE.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_HEADLINE.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_DESCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..03c6ca0b
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+5fd570af72fc242f12aba2cba1815e1cf82b1c86
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_TITLE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_TITLE.png.sha1
new file mode 100644
index 0000000..03c6ca0b
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_TITLE.png.sha1
@@ -0,0 +1 @@
+5fd570af72fc242f12aba2cba1815e1cf82b1c86
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVE_SUBSCRIPTION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVE_SUBSCRIPTION.png.sha1
new file mode 100644
index 0000000..31a855a
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_REMOVE_SUBSCRIPTION.png.sha1
@@ -0,0 +1 @@
+12de7cebe17dd3baa67ac269cf74e6a6f7efa287
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SECTION_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SECTION_LABEL.png.sha1
index c0f5c93..b634ba6 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SECTION_LABEL.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SECTION_LABEL.png.sha1
@@ -1 +1 @@
-9efa3c921bf5a986407ca2002268f2d5cf52f33e
\ No newline at end of file
+9efa3c921bf5a986407ca2002268f2d5cf52f33e
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SOURCE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SOURCE.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SOURCE.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SUBSCRIPTION_EXPIRATION.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SUBSCRIPTION_EXPIRATION.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SUBSCRIPTION_EXPIRATION.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SYSTEM_CA.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SYSTEM_CA.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_SYSTEM_CA.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_TRUSTED_CA.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_TRUSTED_CA.png.sha1
new file mode 100644
index 0000000..7bc4500
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_INTERNET_PASSPOINT_TRUSTED_CA.png.sha1
@@ -0,0 +1 @@
+240cf141f63e5382455e9df329cc1749e9b1cece
\ No newline at end of file
diff --git a/chrome/app/password_manager_ui_strings.grdp b/chrome/app/password_manager_ui_strings.grdp
index d1de8ce..be5c1e29 100644
--- a/chrome/app/password_manager_ui_strings.grdp
+++ b/chrome/app/password_manager_ui_strings.grdp
@@ -126,6 +126,9 @@
   <message name="IDS_PASSWORD_MANAGER_UI_IMPORT_COMPLETE_TITLE" desc="The title of a dialog, which offers the ability for the user to import passwords into Password Manager. Shown when import has successfully finished, but some errors were found in the imported file.">
     Import complete
   </message>
+  <message name="IDS_PASSWORD_MANAGER_UI_IMPORT_DELETE_FILE_OPTION" desc="Message is shown on the dialog for importing passwords, after a successful import. It offers a user to delete the file, which they used for importing passwords from, by clicking at the checkbox and 'Done' button afterwards.">
+    Delete <ph name="FILENAME">&lt;span class="bold-text"&gt;$1&lt;/span&gt;<ex>&lt;span class="bold-text"&gt;filename.csv&lt;/span&gt;</ex></ph>, so others who use this device can't see your passwords
+  </message>
   <message name="IDS_PASSWORD_MANAGER_UI_IMPORT_VIEW_PASSWORDS" desc="A button on the import dialog that redirects the user to the page with the passwords list.">
     View passwords
   </message>
diff --git a/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_IMPORT_DELETE_FILE_OPTION.png.sha1 b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_IMPORT_DELETE_FILE_OPTION.png.sha1
new file mode 100644
index 0000000..ed5bd824
--- /dev/null
+++ b/chrome/app/password_manager_ui_strings_grdp/IDS_PASSWORD_MANAGER_UI_IMPORT_DELETE_FILE_OPTION.png.sha1
@@ -0,0 +1 @@
+b94cf6b0e291f8280e557dc878858c2e72f277e8
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 012e030..b9c28c6 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3106,6 +3106,8 @@
       "password_manager/android/password_manager_error_message_helper_bridge_impl.h",
       "password_manager/android/password_manager_launcher_android.cc",
       "password_manager/android/password_manager_launcher_android.h",
+      "password_manager/android/password_manager_ui_util_android.cc",
+      "password_manager/android/password_manager_ui_util_android.h",
       "password_manager/android/password_store_bridge.cc",
       "password_manager/android/password_store_bridge.h",
       "password_manager/android/password_ui_view_android.cc",
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index c40fc762..59fc6f6 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -5328,7 +5328,6 @@
     "login/ui/login_screen_extension_ui/web_dialog_view_unittest.cc",
     "login/ui/oobe_dialog_size_utils_unittest.cc",
     "login/user_online_signin_notifier_unittest.cc",
-    "login/users/affiliation_unittest.cc",
     "login/users/avatar/user_image_loader_unittest.cc",
     "login/users/default_user_image/default_user_images_unittest.cc",
     "login/users/multi_profile_user_controller_unittest.cc",
diff --git a/chrome/browser/ash/file_manager/file_manager_jstest.cc b/chrome/browser/ash/file_manager/file_manager_jstest.cc
index 66040b2..ce21b7e 100644
--- a/chrome/browser/ash/file_manager/file_manager_jstest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_jstest.cc
@@ -354,6 +354,10 @@
   RunTestURL("state/reducers/bulk_pinning_unittest.js");
 }
 
+IN_PROC_BROWSER_TEST_F(FileManagerJsTest, ReducerPreferences) {
+  RunTestURL("state/reducers/preferences_unittest.js");
+}
+
 IN_PROC_BROWSER_TEST_F(FileManagerJsTest, NudgeContainer) {
   RunTestURL("containers/nudge_container_unittest.js");
 }
diff --git a/chrome/browser/ash/guest_os/public/installer_delegate_factory.cc b/chrome/browser/ash/guest_os/public/installer_delegate_factory.cc
index 8e8c5edf1..8f52c6b 100644
--- a/chrome/browser/ash/guest_os/public/installer_delegate_factory.cc
+++ b/chrome/browser/ash/guest_os/public/installer_delegate_factory.cc
@@ -11,7 +11,6 @@
 std::unique_ptr<ash::guest_os_installer::mojom::PageHandler>
 InstallerDelegateFactory(
     ash::GuestOSInstallerUI* webui,
-    const GURL& url,
     mojo::PendingRemote<ash::guest_os_installer::mojom::Page> pending_page,
     mojo::PendingReceiver<ash::guest_os_installer::mojom::PageHandler>
         pending_page_handler) {
diff --git a/chrome/browser/ash/guest_os/public/installer_delegate_factory.h b/chrome/browser/ash/guest_os/public/installer_delegate_factory.h
index 93720bf2..71ff0a4 100644
--- a/chrome/browser/ash/guest_os/public/installer_delegate_factory.h
+++ b/chrome/browser/ash/guest_os/public/installer_delegate_factory.h
@@ -11,8 +11,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 
-class GURL;
-
 namespace ash {
 class GuestOSInstallerUI;
 }
@@ -22,10 +20,8 @@
 std::unique_ptr<ash::guest_os_installer::mojom::PageHandler>
 InstallerDelegateFactory(
     ash::GuestOSInstallerUI*,
-    const GURL&,
     mojo::PendingRemote<ash::guest_os_installer::mojom::Page>,
     mojo::PendingReceiver<ash::guest_os_installer::mojom::PageHandler>);
-
 }
 
 #endif
diff --git a/chrome/browser/ash/login/saml/saml_browsertest.cc b/chrome/browser/ash/login/saml/saml_browsertest.cc
index 10f1e5e..25909ec 100644
--- a/chrome/browser/ash/login/saml/saml_browsertest.cc
+++ b/chrome/browser/ash/login/saml/saml_browsertest.cc
@@ -1184,7 +1184,7 @@
       user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
 
   // Give affiliated users appropriate affiliation IDs.
-  std::set<std::string> user_affiliation_ids;
+  base::flat_set<std::string> user_affiliation_ids;
   user_affiliation_ids.insert(kAffiliationID);
   ChromeUserManager::Get()->SetUserAffiliation(
       AccountId::FromUserEmailGaiaId(
diff --git a/chrome/browser/ash/login/users/affiliation.cc b/chrome/browser/ash/login/users/affiliation.cc
index dd06ade..3c80a7d 100644
--- a/chrome/browser/ash/login/users/affiliation.cc
+++ b/chrome/browser/ash/login/users/affiliation.cc
@@ -4,16 +4,16 @@
 
 #include "chrome/browser/ash/login/users/affiliation.h"
 
+#include "base/containers/flat_set.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
-#include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/browser_process_platform_part.h"
 #include "components/account_id/account_id.h"
+#include "components/policy/core/common/cloud/affiliation.h"
 #include "components/policy/proto/device_management_backend.pb.h"
-#include "google_apis/gaia/gaia_auth_util.h"
 
 namespace ash {
 namespace {
@@ -21,14 +21,13 @@
 std::string GetDeviceDMTokenIfAffiliated(
     const AccountId& account_id,
     const std::vector<std::string>& user_affiliation_ids) {
-  const AffiliationIDSet set_of_user_affiliation_ids(
-      user_affiliation_ids.begin(), user_affiliation_ids.end());
   const policy::BrowserPolicyConnectorAsh* connector =
       g_browser_process->platform_part()->browser_policy_connector_ash();
   DCHECK(connector);
-  const bool is_affiliated = IsUserAffiliated(
-      set_of_user_affiliation_ids, connector->GetDeviceAffiliationIDs(),
-      account_id.GetUserEmail());
+  const bool is_affiliated = policy::IsUserAffiliated(
+      base::flat_set<std::string>(user_affiliation_ids.begin(),
+                                  user_affiliation_ids.end()),
+      connector->device_affiliation_ids(), account_id.GetUserEmail());
   if (is_affiliated) {
     const enterprise_management::PolicyData* policy_data =
         DeviceSettingsService::Get()->policy_data();
@@ -40,44 +39,6 @@
 
 }  // namespace
 
-bool HaveCommonElement(const std::set<std::string>& set1,
-                       const std::set<std::string>& set2) {
-  std::set<std::string>::const_iterator it1 = set1.begin();
-  std::set<std::string>::const_iterator it2 = set2.begin();
-
-  while (it1 != set1.end() && it2 != set2.end()) {
-    if (*it1 == *it2)
-      return true;
-    if (*it1 < *it2) {
-      ++it1;
-    } else {
-      ++it2;
-    }
-  }
-  return false;
-}
-
-bool IsUserAffiliated(const AffiliationIDSet& user_affiliation_ids,
-                      const AffiliationIDSet& device_affiliation_ids,
-                      const std::string& email) {
-  // An empty username means incognito user in case of Chrome OS and no
-  // logged-in user in case of Chrome (SigninService). Many tests use nonsense
-  // email addresses (e.g. 'test') so treat those as non-enterprise users.
-  if (email.empty() || email.find('@') == std::string::npos) {
-    return false;
-  }
-
-  if (policy::IsDeviceLocalAccountUser(email, nullptr)) {
-    return true;
-  }
-
-  if (!device_affiliation_ids.empty() && !user_affiliation_ids.empty()) {
-    return HaveCommonElement(user_affiliation_ids, device_affiliation_ids);
-  }
-
-  return false;
-}
-
 base::RepeatingCallback<std::string(const std::vector<std::string>&)>
 GetDeviceDMTokenForUserPolicyGetter(const AccountId& account_id) {
   return base::BindRepeating(&GetDeviceDMTokenIfAffiliated, account_id);
diff --git a/chrome/browser/ash/login/users/affiliation.h b/chrome/browser/ash/login/users/affiliation.h
index 389cd84..6134a45 100644
--- a/chrome/browser/ash/login/users/affiliation.h
+++ b/chrome/browser/ash/login/users/affiliation.h
@@ -17,21 +17,6 @@
 
 typedef std::set<std::string> AffiliationIDSet;
 
-// Returns true if there is at least one common element in two sets.
-// Complexity: O(n + m), where n - size of the first set, m - size of
-// the second set.
-bool HaveCommonElement(const std::set<std::string>& set1,
-                       const std::set<std::string>& set2);
-
-// TODO(peletskyi): Remove email after affiliation based implementation will
-// fully work. http://crbug.com/515476
-// The function makes a decision if user with `user_affiliation_ids` and
-// `email` is affiliated on the device with `device_affiliation_ids` and
-// `enterprise_domain`.
-bool IsUserAffiliated(const AffiliationIDSet& user_affiliation_ids,
-                      const AffiliationIDSet& device_affiliation_ids,
-                      const std::string& email);
-
 // Returns a callback to retrieve device DMToken if the user with
 // given `account_id` is affiliated on the device.
 base::RepeatingCallback<std::string(const std::vector<std::string>&)>
diff --git a/chrome/browser/ash/login/users/affiliation_unittest.cc b/chrome/browser/ash/login/users/affiliation_unittest.cc
deleted file mode 100644
index 74b3800..0000000
--- a/chrome/browser/ash/login/users/affiliation_unittest.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2015 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/login/users/affiliation.h"
-
-#include <set>
-#include <string>
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace ash {
-
-TEST(AffiliationTest, HaveCommonElementEmptySet) {
-  // Empty sets don't have common elements.
-  EXPECT_FALSE(HaveCommonElement(AffiliationIDSet(), AffiliationIDSet()));
-
-  AffiliationIDSet not_empty_set;
-  not_empty_set.insert("a");
-
-  // Only first set is empty. No common elements and no crash.
-  EXPECT_FALSE(HaveCommonElement(AffiliationIDSet(), not_empty_set));
-  // Now the second set is empty.
-  EXPECT_FALSE(HaveCommonElement(not_empty_set, AffiliationIDSet()));
-}
-
-TEST(AffiliationTest, HaveCommonElementNoOverlap) {
-  AffiliationIDSet set1;
-  AffiliationIDSet set2;
-
-  // No common elements.
-  set1.insert("a");
-  set1.insert("b");
-  set1.insert("c");
-
-  set2.insert("d");
-  set2.insert("e");
-  set2.insert("f");
-  EXPECT_FALSE(HaveCommonElement(set1, set2));
-}
-
-TEST(AffiliationTest, HaveCommonElementFirstAndLastIsCommon) {
-  AffiliationIDSet set1;
-  AffiliationIDSet set2;
-
-  // The common element is last in set1 and first in set2.
-  set1.insert("a");
-  set1.insert("b");
-  set1.insert("c");
-  set1.insert("d");  // String "d" is common.
-
-  set2.insert("d");
-  set2.insert("e");
-  set2.insert("f");
-
-  EXPECT_TRUE(HaveCommonElement(set1, set2));
-  EXPECT_TRUE(HaveCommonElement(set2, set1));
-}
-
-TEST(AffiliationTest, HaveCommonElementCommonInTheMiddle) {
-  AffiliationIDSet set1;
-  AffiliationIDSet set2;
-
-  // The common element is in the middle of the two sets.
-  set1.insert("b");
-  set1.insert("f");  // String "f" is common.
-  set1.insert("k");
-
-  set2.insert("c");
-  set2.insert("f");
-  set2.insert("j");
-
-  EXPECT_TRUE(HaveCommonElement(set1, set2));
-  EXPECT_TRUE(HaveCommonElement(set2, set1));
-}
-
-TEST(AffiliationTest, Generic) {
-  AffiliationIDSet user_ids;    // User affiliation IDs.
-  AffiliationIDSet device_ids;  // Device affiliation IDs.
-
-  // Empty affiliation IDs.
-  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
-
-  user_ids.insert("aaaa");  // Only user affiliation IDs present.
-  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
-
-  device_ids.insert("bbbb");  // Device and user IDs do not overlap.
-  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
-
-  user_ids.insert("cccc");  // Device and user IDs do overlap.
-  device_ids.insert("cccc");
-  EXPECT_TRUE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
-
-  // Invalid email overrides match of affiliation IDs.
-  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, ""));
-  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user"));
-}
-
-}  // namespace ash
diff --git a/chrome/browser/ash/login/users/chrome_user_manager.h b/chrome/browser/ash/login/users/chrome_user_manager.h
index 1dbfc3a..7ce63e0 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager.h
+++ b/chrome/browser/ash/login/users/chrome_user_manager.h
@@ -5,9 +5,9 @@
 #ifndef CHROME_BROWSER_ASH_LOGIN_USERS_CHROME_USER_MANAGER_H_
 #define CHROME_BROWSER_ASH_LOGIN_USERS_CHROME_USER_MANAGER_H_
 
+#include "base/containers/flat_set.h"
 #include "base/memory/ref_counted.h"
 #include "base/task/single_thread_task_runner.h"
-#include "chrome/browser/ash/login/users/affiliation.h"
 #include "chrome/browser/ash/login/users/user_manager_interface.h"
 #include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
 #include "chrome/browser/ash/policy/core/reporting_user_tracker.h"
@@ -58,7 +58,7 @@
   // judging by `user_affiliation_ids` and device affiliation IDs.
   virtual void SetUserAffiliation(
       const AccountId& account_id,
-      const AffiliationIDSet& user_affiliation_ids) = 0;
+      const base::flat_set<std::string>& user_affiliation_ids) = 0;
 
   // Return whether the given user should be reported (see
   // policy::DeviceStatusCollector).
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
index 6956eac5..2d366eb 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.cc
@@ -93,6 +93,7 @@
 #include "chromeos/dbus/common/dbus_method_call_status.h"
 #include "components/account_id/account_id.h"
 #include "components/crash/core/common/crash_key.h"
+#include "components/policy/core/common/cloud/affiliation.h"
 #include "components/policy/core/common/policy_details.h"
 #include "components/policy/policy_constants.h"
 #include "components/prefs/pref_registry_simple.h"
@@ -1262,14 +1263,14 @@
 
 void ChromeUserManagerImpl::SetUserAffiliation(
     const AccountId& account_id,
-    const AffiliationIDSet& user_affiliation_ids) {
+    const base::flat_set<std::string>& user_affiliation_ids) {
   user_manager::User* user = FindUserAndModify(account_id);
 
   if (user) {
     policy::BrowserPolicyConnectorAsh const* const connector =
         g_browser_process->platform_part()->browser_policy_connector_ash();
-    const bool is_affiliated = IsUserAffiliated(
-        user_affiliation_ids, connector->GetDeviceAffiliationIDs(),
+    const bool is_affiliated = policy::IsUserAffiliated(
+        user_affiliation_ids, connector->device_affiliation_ids(),
         account_id.GetUserEmail());
     user->SetAffiliation(is_affiliated);
     reporting_user_tracker_.OnSetUserAffiliation(*user);
diff --git a/chrome/browser/ash/login/users/chrome_user_manager_impl.h b/chrome/browser/ash/login/users/chrome_user_manager_impl.h
index 7a71779..21150465 100644
--- a/chrome/browser/ash/login/users/chrome_user_manager_impl.h
+++ b/chrome/browser/ash/login/users/chrome_user_manager_impl.h
@@ -136,7 +136,7 @@
   bool IsEnterpriseManaged() const override;
   void SetUserAffiliation(
       const AccountId& account_id,
-      const AffiliationIDSet& user_affiliation_ids) override;
+      const base::flat_set<std::string>& user_affiliation_ids) override;
   bool IsFullManagementDisclosureNeeded(
       policy::DeviceLocalAccountPolicyBroker* broker) const override;
 
diff --git a/chrome/browser/ash/login/users/fake_chrome_user_manager.cc b/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
index c1153ded..b8b77c0 100644
--- a/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
+++ b/chrome/browser/ash/login/users/fake_chrome_user_manager.cc
@@ -725,7 +725,7 @@
 
 void FakeChromeUserManager::SetUserAffiliation(
     const AccountId& account_id,
-    const AffiliationIDSet& user_affiliation_ids) {}
+    const base::flat_set<std::string>& user_affiliation_ids) {}
 
 void FakeChromeUserManager::SetUserAffiliationForTesting(
     const AccountId& account_id,
diff --git a/chrome/browser/ash/login/users/fake_chrome_user_manager.h b/chrome/browser/ash/login/users/fake_chrome_user_manager.h
index 5b0bd5cb..dae55eb 100644
--- a/chrome/browser/ash/login/users/fake_chrome_user_manager.h
+++ b/chrome/browser/ash/login/users/fake_chrome_user_manager.h
@@ -172,7 +172,7 @@
   // ChromeUserManager override.
   void SetUserAffiliation(
       const AccountId& account_id,
-      const AffiliationIDSet& user_affiliation_ids) override;
+      const base::flat_set<std::string>& user_affiliation_ids) override;
   bool IsFullManagementDisclosureNeeded(
       policy::DeviceLocalAccountPolicyBroker* broker) const override;
 
diff --git a/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc b/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
index b36430c..9fa72586 100644
--- a/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
+++ b/chrome/browser/ash/policy/active_directory/active_directory_policy_manager.cc
@@ -327,16 +327,15 @@
 
 void UserActiveDirectoryPolicyManager::OnPublishPolicy() {
   const em::PolicyData* policy_data = store()->policy();
-  if (!policy_data)
+  if (!policy_data) {
     return;
+  }
 
   // Update user affiliation IDs.
-  ash::AffiliationIDSet set_of_user_affiliation_ids(
-      policy_data->user_affiliation_ids().begin(),
-      policy_data->user_affiliation_ids().end());
-
   ash::ChromeUserManager::Get()->SetUserAffiliation(
-      account_id_, set_of_user_affiliation_ids);
+      account_id_,
+      base::flat_set<std::string>(policy_data->user_affiliation_ids().begin(),
+                                  policy_data->user_affiliation_ids().end()));
 }
 
 void UserActiveDirectoryPolicyManager::OnBlockingFetchTimeout() {
diff --git a/chrome/browser/ash/policy/arc/android_management_client.cc b/chrome/browser/ash/policy/arc/android_management_client.cc
index 5449c22..1b139a0 100644
--- a/chrome/browser/ash/policy/arc/android_management_client.cc
+++ b/chrome/browser/ash/policy/arc/android_management_client.cc
@@ -8,8 +8,8 @@
 
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
-#include "base/guid.h"
 #include "base/logging.h"
+#include "base/uuid.h"
 #include "components/policy/core/common/cloud/device_management_service.h"
 #include "components/policy/core/common/cloud/dm_auth.h"
 #include "components/policy/core/common/cloud/dmserver_job_configurations.h"
@@ -86,7 +86,7 @@
       DMServerJobConfiguration>(
       device_management_service_,
       DeviceManagementService::JobConfiguration::TYPE_ANDROID_MANAGEMENT_CHECK,
-      /*client_id=*/base::GenerateGUID(),
+      /*client_id=*/base::Uuid::GenerateRandomV4().AsLowercaseString(),
       /*critical=*/false, DMAuth::NoAuth(), access_token, url_loader_factory_,
       base::BindOnce(&AndroidManagementClientImpl::OnAndroidManagementChecked,
                      weak_ptr_factory_.GetWeakPtr()));
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
index 7364b056..92b38ff 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.cc
@@ -594,12 +594,6 @@
   return {};
 }
 
-ash::AffiliationIDSet BrowserPolicyConnectorAsh::GetDeviceAffiliationIDs()
-    const {
-  base::flat_set<std::string> affiliation_ids = device_affiliation_ids();
-  return {affiliation_ids.begin(), affiliation_ids.end()};
-}
-
 const em::PolicyData* BrowserPolicyConnectorAsh::GetDevicePolicy() const {
   if (device_cloud_policy_manager_)
     return device_cloud_policy_manager_->device_store()->policy();
diff --git a/chrome/browser/ash/policy/core/browser_policy_connector_ash.h b/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
index d587392..0e95546 100644
--- a/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
+++ b/chrome/browser/ash/policy/core/browser_policy_connector_ash.h
@@ -14,7 +14,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/ash/cert_provisioning/cert_provisioning_scheduler.h"
-#include "chrome/browser/ash/login/users/affiliation.h"
 #include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
@@ -241,10 +240,7 @@
   void OnDeviceCloudPolicyManagerConnected() override;
   void OnDeviceCloudPolicyManagerGotRegistry() override;
 
-  // TODO(crbug.com/1187628): Combine the following two functions into one to
-  // simplify the API.
   base::flat_set<std::string> device_affiliation_ids() const override;
-  ash::AffiliationIDSet GetDeviceAffiliationIDs() const;
 
   // BrowserPolicyConnector:
   // Always returns true as command line flag can be set under dev mode only.
diff --git a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
index 61930a2..acf50ed 100644
--- a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
+++ b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.cc
@@ -67,22 +67,8 @@
 namespace {
 
 // UMA histogram names.
-const char kUMADelayInitialization[] =
-    "Enterprise.UserPolicyChromeOS.DelayInitialization";
-const char kUMAInitialFetchClientError[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.ClientError";
-const char kUMAInitialFetchDelayClientRegister[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.DelayClientRegister";
-const char kUMAInitialFetchDelayOAuth2Token[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.DelayOAuth2Token";
-const char kUMAInitialFetchDelayPolicyFetch[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.DelayPolicyFetch";
-const char kUMAInitialFetchDelayTotal[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.DelayTotal";
 const char kUMAInitialFetchOAuth2Error[] =
     "Enterprise.UserPolicyChromeOS.InitialFetch.OAuth2Error";
-const char kUMAInitialFetchOAuth2NetworkError[] =
-    "Enterprise.UserPolicyChromeOS.InitialFetch.OAuth2NetworkError";
 const char kUMAReregistrationResult[] =
     "Enterprise.UserPolicyChromeOS.ReregistrationResult";
 
@@ -169,7 +155,6 @@
       fatal_error_callback_(std::move(fatal_error_callback)) {
   DCHECK(profile_);
   DCHECK(local_state_);
-  time_init_started_ = base::Time::Now();
 
   // Some tests don't want to complete policy initialization until they have
   // manually injected policy even though the profile itself is synchronously
@@ -370,10 +355,6 @@
 void UserCloudPolicyManagerAsh::OnCloudPolicyServiceInitializationCompleted() {
   service()->RemoveObserver(this);
 
-  time_init_completed_ = base::Time::Now();
-  UMA_HISTOGRAM_MEDIUM_TIMES(kUMADelayInitialization,
-                             time_init_completed_ - time_init_started_);
-
   // If the CloudPolicyClient isn't registered at this stage then it needs an
   // OAuth token for the initial registration (there's no cached policy).
   //
@@ -440,13 +421,6 @@
   }
 
   if (waiting_for_policy_fetch_) {
-    time_client_registered_ = base::Time::Now();
-    if (!time_token_available_.is_null()) {
-      UMA_HISTOGRAM_MEDIUM_TIMES(
-          kUMAInitialFetchDelayClientRegister,
-          time_client_registered_ - time_token_available_);
-    }
-
     // If we're blocked on the policy fetch, now is a good time to issue it.
     if (client()->is_registered()) {
       service()->RefreshPolicy(base::BindOnce(
@@ -463,10 +437,6 @@
 void UserCloudPolicyManagerAsh::OnClientError(
     CloudPolicyClient* cloud_policy_client) {
   DCHECK_EQ(client(), cloud_policy_client);
-  if (waiting_for_policy_fetch_) {
-    base::UmaHistogramSparse(kUMAInitialFetchClientError,
-                             cloud_policy_client->last_dm_status());
-  }
   switch (client()->last_dm_status()) {
     case DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED:
       // If management is not supported for this user, then a registration
@@ -532,12 +502,10 @@
     enforcement_type_ = PolicyEnforcement::kPolicyOptional;
 
     DCHECK(policy_data->has_username());
-    ash::AffiliationIDSet set_of_user_affiliation_ids(
-        policy_data->user_affiliation_ids().begin(),
-        policy_data->user_affiliation_ids().end());
-
     ash::ChromeUserManager::Get()->SetUserAffiliation(
-        account_id_, set_of_user_affiliation_ids);
+        account_id_,
+        base::flat_set<std::string>(policy_data->user_affiliation_ids().begin(),
+                                    policy_data->user_affiliation_ids().end()));
   }
 }
 
@@ -636,11 +604,6 @@
     const std::string& policy_token,
     const GoogleServiceAuthError& error) {
   DCHECK(!client()->is_registered());
-  time_token_available_ = base::Time::Now();
-  if (waiting_for_policy_fetch_) {
-    UMA_HISTOGRAM_MEDIUM_TIMES(kUMAInitialFetchDelayOAuth2Token,
-                               time_token_available_ - time_init_completed_);
-  }
 
   if (error.state() == GoogleServiceAuthError::NONE) {
     if (RequiresOAuthTokenForChildUser())
@@ -661,12 +624,6 @@
   } else {
     UMA_HISTOGRAM_ENUMERATION(kUMAInitialFetchOAuth2Error, error.state(),
                               GoogleServiceAuthError::NUM_STATES);
-    if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED) {
-      // Network errors are negative in the code, but the histogram data type
-      // expects the corresponding positive value.
-      base::UmaHistogramSparse(kUMAInitialFetchOAuth2NetworkError,
-                               -error.network_error());
-    }
     // Failed to get a token, stop waiting if policy is not required for this
     // user.
     CancelWaitForPolicyFetch(
@@ -677,11 +634,6 @@
 }
 
 void UserCloudPolicyManagerAsh::OnInitialPolicyFetchComplete(bool success) {
-  const base::Time now = base::Time::Now();
-  UMA_HISTOGRAM_MEDIUM_TIMES(kUMAInitialFetchDelayPolicyFetch,
-                             now - time_client_registered_);
-  UMA_HISTOGRAM_MEDIUM_TIMES(kUMAInitialFetchDelayTotal,
-                             now - time_init_started_);
   CancelWaitForPolicyFetch(
       success,
       "policy fetch complete"
diff --git a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h
index 0a24ec1..56fd0ea 100644
--- a/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h
+++ b/chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h
@@ -302,12 +302,6 @@
   // called later.
   std::string access_token_;
 
-  // Timestamps for collecting timing UMA stats.
-  base::Time time_init_started_;
-  base::Time time_init_completed_;
-  base::Time time_token_available_;
-  base::Time time_client_registered_;
-
   // The AccountId associated with the user whose policy is being loaded.
   const AccountId account_id_;
 
diff --git a/chrome/browser/ash/policy/enrollment/account_status_check_fetcher.cc b/chrome/browser/ash/policy/enrollment/account_status_check_fetcher.cc
index c993d844..c4921ef 100644
--- a/chrome/browser/ash/policy/enrollment/account_status_check_fetcher.cc
+++ b/chrome/browser/ash/policy/enrollment/account_status_check_fetcher.cc
@@ -8,9 +8,9 @@
 #include <utility>
 
 #include "base/functional/bind.h"
-#include "base/guid.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/uuid.h"
 #include "chrome/browser/ash/arc/arc_optin_uma.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
@@ -95,7 +95,7 @@
     : email_(canonicalized_email),
       service_(service),
       url_loader_factory_(url_loader_factory),
-      random_device_id_(base::GenerateGUID()) {}
+      random_device_id_(base::Uuid::GenerateRandomV4().AsLowercaseString()) {}
 
 AccountStatusCheckFetcher::~AccountStatusCheckFetcher() = default;
 
diff --git a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
index f3ba08c..cf792ab 100644
--- a/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
+++ b/chrome/browser/ash/policy/enrollment/auto_enrollment_client_impl.cc
@@ -11,7 +11,6 @@
 #include "base/check.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
-#include "base/guid.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
@@ -19,6 +18,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
+#include "base/uuid.h"
 #include "base/values.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_state_message_processor.h"
 #include "chrome/browser/ash/policy/enrollment/psm/rlwe_dmserver_client.h"
@@ -741,7 +741,8 @@
     const std::string& server_backed_state_key,
     int power_initial,
     int power_limit) {
-  const std::string device_id = base::GenerateGUID();
+  const std::string device_id =
+      base::Uuid::GenerateRandomV4().AsLowercaseString();
   return base::WrapUnique(new AutoEnrollmentClientImpl(
       progress_callback,
       std::make_unique<FREServerStateAvailabilityRequester>(
@@ -769,7 +770,8 @@
           std::move(psm_rlwe_dmserver_client), local_state),
       std::make_unique<ServerStateRetriever>(
           device_management_service, url_loader_factory, local_state,
-          /*device_id=*/base::GenerateGUID(), kUMASuffixInitialEnrollment,
+          /*device_id=*/base::Uuid::GenerateRandomV4().AsLowercaseString(),
+          kUMASuffixInitialEnrollment,
           AutoEnrollmentStateMessageProcessor::CreateForInitialEnrollment(
               device_serial_number, device_brand_code))));
 }
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_handler.cc b/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
index dbfaa90..0eba472 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_handler.cc
@@ -11,7 +11,6 @@
 #include "base/base64.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
-#include "base/guid.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/task/sequenced_task_runner.h"
diff --git a/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc b/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
index 16c1c80..8bd557b3 100644
--- a/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
+++ b/chrome/browser/ash/policy/enrollment/enrollment_state_fetcher.cc
@@ -11,12 +11,12 @@
 #include "ash/constants/ash_switches.h"
 #include "base/check.h"
 #include "base/functional/callback_forward.h"
-#include "base/guid.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
 #include "base/time/time.h"
 #include "base/types/expected.h"
+#include "base/uuid.h"
 #include "base/values.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/enrollment/auto_enrollment_client.h"
@@ -289,7 +289,7 @@
         context.device_management_service,
         DeviceManagementService::JobConfiguration::
             TYPE_PSM_HAS_DEVICE_STATE_REQUEST,
-        base::GUID::GenerateRandomV4().AsLowercaseString(),
+        base::Uuid::GenerateRandomV4().AsLowercaseString(),
         /*critical=*/true, DMAuth::NoAuth(),
         /*oauth_token=*/absl::nullopt, context.url_loader_factory,
         base::BindOnce(&RlweOprf::OnRequestDone, weak_factory_.GetWeakPtr(),
@@ -382,7 +382,7 @@
         context.device_management_service,
         DeviceManagementService::JobConfiguration::
             TYPE_PSM_HAS_DEVICE_STATE_REQUEST,
-        base::GUID::GenerateRandomV4().AsLowercaseString(),
+        base::Uuid::GenerateRandomV4().AsLowercaseString(),
         /*critical=*/true, DMAuth::NoAuth(),
         /*oauth_token=*/absl::nullopt, context.url_loader_factory,
         base::BindOnce(&RlweQuery::OnRequestDone, weak_factory_.GetWeakPtr(),
@@ -493,7 +493,7 @@
     auto config = std::make_unique<DMServerJobConfiguration>(
         context.device_management_service,
         DeviceManagementService::JobConfiguration::TYPE_DEVICE_STATE_RETRIEVAL,
-        base::GUID::GenerateRandomV4().AsLowercaseString(),
+        base::Uuid::GenerateRandomV4().AsLowercaseString(),
         /*critical=*/true, DMAuth::NoAuth(),
         /*oauth_token=*/absl::nullopt, context.url_loader_factory,
         base::BindOnce(&EnrollmentState::OnRequestDone,
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index 9e41986..1572ff2e 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -241,6 +241,19 @@
         << GetCookiesTreeModelInfo(model->GetRoot());
   }
 
+  inline void ExpectCookieTreeModelCount(int expected3PSPDisabled,
+                                         int expected3PSPEnabled) {
+    // TODO(crbug.com/1307796): Use a different approach to determine presence
+    // of data that does not depend on UI code and has a better resolution when
+    // 3PSP is fully enabled.
+    if (base::FeatureList::IsEnabled(
+            net::features::kThirdPartyStoragePartitioning)) {
+      ExpectCookieTreeModelCount(expected3PSPEnabled);
+    } else {
+      ExpectCookieTreeModelCount(expected3PSPDisabled);
+    }
+  }
+
   void OnVideoDecodePerfInfo(base::RunLoop* run_loop,
                              bool* out_is_smooth,
                              bool* out_is_power_efficient,
@@ -1475,8 +1488,11 @@
   // Expect all datatypes from above except SessionStorage and possibly
   // MediaLicense. SessionStorage is not supported by the CookieTreeModel yet.
   // MediaLicense is integrated into the quota node, which is not yet fully
-  // hooked into CookieTreeModel (see crbug.com/1307796).
-  ExpectCookieTreeModelCount(kStorageTypes.size() - 2);
+  // hooked into CookieTreeModel. When 3PSP is enabled, only Cookies and
+  // LocalStorage are counted. TODO(crbug.com/1307796): Use a different approach
+  // to determine presence of data that does not depend on UI code and has a
+  // better resolution when 3PSP is fully enabled.
+  ExpectCookieTreeModelCount(kStorageTypes.size() - 2, 2);
   RemoveAndWait(chrome_browsing_data_remover::DATA_TYPE_SITE_DATA |
                 content::BrowsingDataRemover::DATA_TYPE_CACHE |
                 chrome_browsing_data_remover::DATA_TYPE_HISTORY |
@@ -1528,8 +1544,11 @@
     EXPECT_TRUE(HasDataForType(type));
   }
   // Expect the datatypes from above except SessionStorage. SessionStorage is
-  // not supported by the CookieTreeModel yet.
-  ExpectCookieTreeModelCount(kSessionOnlyStorageTestTypes.size() - 1);
+  // not supported by the CookieTreeModel yet. When 3PSP is enabled, only
+  // Cookies and LocalStorage are counted. TODO(crbug.com/1307796): Use a
+  // different approach to determine presence of data that does not depend on UI
+  // code and has a better resolution when 3PSP is fully enabled.
+  ExpectCookieTreeModelCount(kSessionOnlyStorageTestTypes.size() - 1, 2);
   HostContentSettingsMapFactory::GetForProfile(GetBrowser()->profile())
       ->SetDefaultContentSetting(ContentSettingsType::COOKIES,
                                  CONTENT_SETTING_SESSION_ONLY);
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
index 5a24e5b6..6414f21 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest_base.cc
@@ -379,11 +379,7 @@
   int count = 0;
   for (const auto& node : root->children()) {
     EXPECT_GE(node->children().size(), 1u);
-    count += base::ranges::count_if(node->children(), [](const auto& child) {
-      // TODO(crbug.com/1307796): Include quota nodes.
-      return child->GetDetailedInfo().node_type !=
-             CookieTreeNode::DetailedInfo::TYPE_QUOTA;
-    });
+    count += node->children().size();
   }
   return count;
 }
diff --git a/chrome/browser/chrome_service_worker_browsertest.cc b/chrome/browser/chrome_service_worker_browsertest.cc
index f074a342..29736d13 100644
--- a/chrome/browser/chrome_service_worker_browsertest.cc
+++ b/chrome/browser/chrome_service_worker_browsertest.cc
@@ -537,13 +537,6 @@
     InitializeServiceWorkerFetchTestPage();
   }
 
-  std::string ExecuteScriptAndExtractString(const std::string& js) {
-    std::string result;
-    EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-        browser()->tab_strip_model()->GetActiveWebContents(), js, &result));
-    return result;
-  }
-
   std::string RequestString(const std::string& url,
                             const std::string& mode,
                             const std::string& credentials,
@@ -714,7 +707,9 @@
                            url.c_str()));
     ExecuteJavaScriptForTests(js);
     waiter.Wait();
-    return ExecuteScriptAndExtractString("reportRequests();");
+    return EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                  "reportRequests();", content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
+        .ExtractString();
   }
 
   void CopyTestFile(const std::string& src, const std::string& dst) {
@@ -751,9 +746,11 @@
         .GetManifest(
             base::BindOnce(&ManifestCallbackAndRun, run_loop.QuitClosure()));
     run_loop.Run();
-    return ExecuteScriptAndExtractString(
-        "if (issuedRequests.length != 0) reportRequests();"
-        "else reportOnFetch = true;");
+    return EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                  "if (issuedRequests.length != 0) reportRequests();"
+                  "else reportOnFetch = true;",
+                  content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
+        .ExtractString();
   }
 
   static void ManifestCallbackAndRun(base::OnceClosure continuation,
@@ -853,14 +850,20 @@
   }
 
   std::string ExecutePNACLUrlLoaderTest(const std::string& mode) {
-    std::string result(ExecuteScriptAndExtractString(
-        base::StringPrintf("reportOnFetch = false;"
-                           "var iframe = document.createElement('iframe');"
-                           "iframe.src='%s#%s';"
-                           "document.body.appendChild(iframe);",
-                           test_page_url_.c_str(), mode.c_str())));
+    std::string result(
+        EvalJs(
+            browser()->tab_strip_model()->GetActiveWebContents(),
+            base::StringPrintf("reportOnFetch = false;"
+                               "var iframe = document.createElement('iframe');"
+                               "iframe.src='%s#%s';"
+                               "document.body.appendChild(iframe);",
+                               test_page_url_.c_str(), mode.c_str()),
+            content::EXECUTE_SCRIPT_USE_MANUAL_REPLY)
+            .ExtractString());
     EXPECT_EQ(base::StringPrintf("OnOpen%s", mode.c_str()), result);
-    return ExecuteScriptAndExtractString("reportRequests();");
+    return EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                  "reportRequests();")
+        .ExtractString();
   }
 
  private:
diff --git a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
index eacad2a..5860e23 100644
--- a/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/chromeos_permission_messages_unittest.cc
@@ -46,7 +46,7 @@
 const std::u16string kTelemetryNetworkInformationPermissionMessage =
     u"Read ChromeOS network information";
 const std::u16string kAttachedDeviceInfo =
-    u"Read attached device information and data";
+    u"Read attached devices information and data";
 }  // namespace
 
 // Tests that ChromePermissionMessageProvider provides not only correct, but
diff --git a/chrome/browser/download/bubble/download_bubble_ui_controller.cc b/chrome/browser/download/bubble/download_bubble_ui_controller.cc
index 801aaa5..0f7edd9 100644
--- a/chrome/browser/download/bubble/download_bubble_ui_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_ui_controller.cc
@@ -63,7 +63,6 @@
     : browser_(browser),
       profile_(browser->profile()),
       update_service_(update_service),
-      download_manager_(profile_->GetDownloadManager()),
       offline_manager_(
           OfflineItemModelManagerFactory::GetForBrowserContext(profile_)) {}
 
@@ -78,10 +77,6 @@
   display_controller_->HandleButtonPressed();
 }
 
-void DownloadBubbleUIController::OnDownloadManagerGoingDown() {
-  download_manager_ = nullptr;
-}
-
 void DownloadBubbleUIController::OnOfflineItemsAdded(
     const OfflineContentProvider::OfflineItemList& items) {
   display_controller_->OnNewItem(/*show_animation=*/false);
@@ -147,6 +142,9 @@
 std::vector<DownloadUIModelPtr> DownloadBubbleUIController::GetDownloadUIModels(
     bool is_main_view) {
   std::vector<DownloadUIModelPtr> all_items;
+  if (!update_service_->IsInitialized()) {
+    return all_items;
+  }
   update_service_->GetAllModelsToDisplay(
       all_items, /*force_backfill_download_items=*/true);
   std::vector<DownloadUIModelPtr> items_to_return;
@@ -246,7 +244,8 @@
     DownloadCommands::Command command) {
   DCHECK(command == DownloadCommands::RETRY);
   display_controller_->HideBubble();
-  if (!download_manager_) {
+  content::DownloadManager* download_manager = profile_->GetDownloadManager();
+  if (!download_manager) {
     return;
   }
   RecordDownloadRetry(
@@ -281,7 +280,7 @@
   download_url_params->set_download_source(
       download::DownloadSource::RETRY_FROM_BUBBLE);
 
-  download_manager_->DownloadUrl(std::move(download_url_params));
+  download_manager->DownloadUrl(std::move(download_url_params));
 }
 
 void DownloadBubbleUIController::ScheduleCancelForEphemeralWarning(
diff --git a/chrome/browser/download/bubble/download_bubble_ui_controller.h b/chrome/browser/download/bubble/download_bubble_ui_controller.h
index 2a57a6b..8db427b6 100644
--- a/chrome/browser/download/bubble/download_bubble_ui_controller.h
+++ b/chrome/browser/download/bubble/download_bubble_ui_controller.h
@@ -14,7 +14,6 @@
 #include "components/download/content/public/all_download_item_notifier.h"
 #include "components/offline_items_collection/core/offline_content_aggregator.h"
 #include "components/offline_items_collection/core/offline_content_provider.h"
-#include "content/public/browser/download_manager.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class Profile;
@@ -47,7 +46,6 @@
                            bool may_show_animation);
   void OnDownloadItemUpdated(download::DownloadItem* item);
   void OnDownloadItemRemoved(download::DownloadItem* item);
-  void OnDownloadManagerGoingDown();
   void OnOfflineItemsAdded(
       const OfflineContentProvider::OfflineItemList& items);
   void OnOfflineItemUpdated(const OfflineItem& item);
@@ -102,10 +100,6 @@
 
   DownloadBubbleUpdateService* update_service() { return update_service_; }
 
-  void set_manager_for_testing(content::DownloadManager* manager) {
-    download_manager_ = manager;
-  }
-
  private:
   friend class DownloadBubbleUIControllerTest;
   friend class DownloadBubbleUIControllerIncognitoTest;
@@ -120,7 +114,6 @@
   raw_ptr<Browser, DanglingUntriaged> browser_;
   raw_ptr<Profile, DanglingUntriaged> profile_;
   raw_ptr<DownloadBubbleUpdateService> update_service_;
-  raw_ptr<content::DownloadManager, DanglingUntriaged> download_manager_;
   raw_ptr<OfflineItemModelManager, DanglingUntriaged> offline_manager_;
 
   // DownloadDisplayController and DownloadBubbleUIController have the same
diff --git a/chrome/browser/download/bubble/download_bubble_ui_controller_unittest.cc b/chrome/browser/download/bubble/download_bubble_ui_controller_unittest.cc
index 20442eb..dde8288 100644
--- a/chrome/browser/download/bubble/download_bubble_ui_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_bubble_ui_controller_unittest.cc
@@ -123,6 +123,8 @@
 
   void AddModel(ModelType type) { model_types_.push_back(type); }
 
+  bool IsInitialized() const override { return true; }
+
   MOCK_METHOD(DownloadDisplayController::ProgressInfo,
               GetProgressInfo,
               (),
@@ -184,8 +186,6 @@
     second_display_controller_ =
         std::make_unique<NiceMock<MockDownloadDisplayController>>(
             browser_.get(), second_controller_.get());
-    controller_->set_manager_for_testing(manager_);
-    second_controller_->set_manager_for_testing(manager_);
   }
 
   void TearDown() override {
diff --git a/chrome/browser/download/bubble/download_bubble_update_service.cc b/chrome/browser/download/bubble/download_bubble_update_service.cc
index a65918b..e75e55d 100644
--- a/chrome/browser/download/bubble/download_bubble_update_service.cc
+++ b/chrome/browser/download/bubble/download_bubble_update_service.cc
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "chrome/browser/content_index/content_index_provider_impl.h"
 #include "chrome/browser/download/bubble/download_bubble_ui_controller.h"
+#include "chrome/browser/download/bubble/download_bubble_update_service_factory.h"
 #include "chrome/browser/download/bubble/download_bubble_utils.h"
 #include "chrome/browser/download/bubble/download_display_controller.h"
 #include "chrome/browser/download/download_crx_util.h"
@@ -166,19 +167,11 @@
 
 DownloadBubbleUpdateService::DownloadBubbleUpdateService(Profile* profile)
     : profile_(profile),
-      original_profile_(
-          profile->IsOffTheRecord() ? profile_->GetOriginalProfile() : nullptr),
-      download_item_notifier_(profile->GetDownloadManager(), this),
-      original_download_item_notifier_(
-          profile->IsOffTheRecord()
-              ? absl::make_optional<download::AllDownloadItemNotifier>(
-                    original_profile_->GetDownloadManager(),
-                    this)
-              : absl::nullopt) {
+      original_profile_(profile->IsOffTheRecord()
+                            ? profile_->GetOriginalProfile()
+                            : nullptr) {
   offline_content_provider_observation_.Observe(
       OfflineContentAggregatorFactory::GetForKey(profile_->GetProfileKey()));
-  InitializeDownloadItemsCache();
-  StartInitializeOfflineItemsCache();
 }
 
 DownloadBubbleUpdateService::~DownloadBubbleUpdateService() = default;
@@ -206,6 +199,72 @@
   return cache.size() == GetNumItemsToCache();
 }
 
+void DownloadBubbleUpdateService::Initialize(
+    content::DownloadManager* manager) {
+  CHECK(manager);
+  CHECK(!download_item_notifier_);
+
+  // Assume we have an original profile and it has an OTR profile.
+  // If the original profile's DownloadBubbleUpdateService is Initialize()'d
+  // already when this function is invoked on the OTR profile's
+  // DownloadBubbleUpdateService, we set the OTR profile's
+  // DownloadBubbleUpdateService's |original_download_item_notifier_| in the
+  // 'if' block below. If the original profile's DownloadBubbleUpdateService is
+  // not yet initialized when this function is invoked on the OTR profile's
+  // DownloadBubbleUpdateService, we will set the OTR profile's
+  // DownloadBubbleUpdateService's |original_download_item_notifier_| when the
+  // original profile's DownloadBubbleUpdateService does become Initialize()'d,
+  // in the 'else' block below (which will trigger re-intialization of the
+  // download item cache).
+  if (profile_->IsOffTheRecord()) {
+    DownloadBubbleUpdateService* original_update_service =
+        DownloadBubbleUpdateServiceFactory::GetForProfile(original_profile_);
+    content::DownloadManager* original_download_manager =
+        original_update_service ? original_update_service->GetDownloadManager()
+                                : nullptr;
+    if (original_download_manager) {
+      InitializeOriginalNotifier(original_download_manager);
+    }
+  } else {
+    for (Profile* otr_profile : profile_->GetAllOffTheRecordProfiles()) {
+      DownloadBubbleUpdateServiceFactory::GetForProfile(otr_profile)
+          ->InitializeOriginalNotifier(manager);
+    }
+  }
+  download_item_notifier_ =
+      std::make_unique<download::AllDownloadItemNotifier>(manager, this);
+  // As long as we have a notifier for this profile, we can initialize the cache
+  // with the current profile's downloads. If we get an original manager in the
+  // future, we will initialize from scratch at that time.
+  InitializeDownloadItemsCache();
+  StartInitializeOfflineItemsCache();
+}
+
+void DownloadBubbleUpdateService::InitializeOriginalNotifier(
+    content::DownloadManager* manager) {
+  CHECK(profile_->IsOffTheRecord());
+  CHECK(manager);
+  if (original_download_item_notifier_) {
+    return;
+  }
+  original_download_item_notifier_ =
+      std::make_unique<download::AllDownloadItemNotifier>(manager, this);
+  // Reset the download items cache, now that we have an original
+  // DownloadManager to pull from.
+  if (download_item_notifier_) {
+    InitializeDownloadItemsCache();
+  }
+}
+
+content::DownloadManager* DownloadBubbleUpdateService::GetDownloadManager() {
+  return download_item_notifier_ ? download_item_notifier_->GetManager()
+                                 : nullptr;
+}
+
+bool DownloadBubbleUpdateService::IsInitialized() const {
+  return download_item_notifier_ && offline_items_initialized_;
+}
+
 bool DownloadBubbleUpdateService::GetAllModelsToDisplay(
     std::vector<DownloadUIModelPtr>& models,
     bool force_backfill_download_items) {
@@ -343,7 +402,8 @@
 void DownloadBubbleUpdateService::OnDownloadCreated(
     content::DownloadManager* manager,
     download::DownloadItem* item) {
-  if (download_manager_shut_down_) {
+  CHECK(download_item_notifier_ || original_download_item_notifier_);
+  if (!download_item_notifier_) {
     return;
   }
   if (download_crx_util::IsExtensionDownload(*item) &&
@@ -364,13 +424,14 @@
 
 void DownloadBubbleUpdateService::OnDelayedCrxDownloadCreated(
     const std::string& guid) {
-  if (download_manager_shut_down_) {
+  CHECK(download_item_notifier_ || original_download_item_notifier_);
+  if (!download_item_notifier_) {
     return;
   }
   // This assumes that for extension/theme downloads, the DownloadItem is
   // removed from the DownloadManager upon completion.
   download::DownloadItem* item =
-      download_item_notifier_.GetManager()->GetDownloadByGuid(guid);
+      download_item_notifier_->GetManager()->GetDownloadByGuid(guid);
   if (item && !item->IsDone()) {
     MaybeAddDownloadItemToCache(item, /*is_new=*/true);
 
@@ -391,7 +452,8 @@
 void DownloadBubbleUpdateService::OnDownloadUpdated(
     content::DownloadManager* manager,
     download::DownloadItem* item) {
-  if (download_manager_shut_down_) {
+  CHECK(download_item_notifier_ || original_download_item_notifier_);
+  if (!download_item_notifier_) {
     return;
   }
   // If the item is an extension or theme download waiting out its 2-second
@@ -421,7 +483,8 @@
 void DownloadBubbleUpdateService::OnDownloadRemoved(
     content::DownloadManager* manager,
     download::DownloadItem* item) {
-  if (download_manager_shut_down_) {
+  CHECK(download_item_notifier_ || original_download_item_notifier_);
+  if (!download_item_notifier_) {
     return;
   }
   bool cache_was_at_max = IsCacheAtMax(download_items_);
@@ -442,21 +505,15 @@
 
 void DownloadBubbleUpdateService::OnManagerGoingDown(
     content::DownloadManager* manager) {
+  CHECK(download_item_notifier_ || original_download_item_notifier_);
   // Assume that the original manager (if this is an OTR profile) may or may not
   // have shut down, but we still want to cease all operations when this
   // profile's manager shuts down.
-  if (manager == profile_->GetDownloadManager()) {
-    download_manager_shut_down_ = true;
+  if (download_item_notifier_ &&
+      (manager == download_item_notifier_->GetManager())) {
     download_items_.clear();
     download_items_iter_map_.clear();
-    for (Browser* browser : chrome::FindAllBrowsersWithProfile(profile_)) {
-      if (browser->window() &&
-          browser->window()->GetDownloadBubbleUIController()) {
-        browser->window()
-            ->GetDownloadBubbleUIController()
-            ->OnDownloadManagerGoingDown();
-      }
-    }
+    download_item_notifier_.reset();
   }
 }
 
@@ -677,7 +734,7 @@
 
 void DownloadBubbleUpdateService::BackfillDownloadItems(
     const ItemSortKey& last_key) {
-  if (download_manager_shut_down_) {
+  if (!download_item_notifier_) {
     return;
   }
   BackfillItemsImpl(last_key, GetAllDownloadItems(), download_items_,
@@ -721,6 +778,7 @@
 }
 
 void DownloadBubbleUpdateService::InitializeDownloadItemsCache() {
+  CHECK(download_item_notifier_);
   download_items_.clear();
   download_items_iter_map_.clear();
   for (download::DownloadItem* item : GetAllDownloadItems()) {
@@ -729,6 +787,9 @@
 }
 
 void DownloadBubbleUpdateService::StartInitializeOfflineItemsCache() {
+  if (offline_items_initialized_) {
+    return;
+  }
   offline_items_collection::OfflineContentProvider* provider =
       OfflineContentAggregatorFactory::GetForKey(profile_->GetProfileKey());
   provider->GetAllItems(
@@ -753,17 +814,11 @@
 std::vector<download::DownloadItem*>
 DownloadBubbleUpdateService::GetAllDownloadItems() {
   std::vector<download::DownloadItem*> all_items;
-  content::DownloadManager* manager = download_item_notifier_.GetManager();
-  if (!manager) {
-    return all_items;
+  if (download_item_notifier_) {
+    download_item_notifier_->GetManager()->GetAllDownloads(&all_items);
   }
-  manager->GetAllDownloads(&all_items);
   if (original_download_item_notifier_) {
-    content::DownloadManager* original_manager =
-        original_download_item_notifier_->GetManager();
-    if (original_manager) {
-      original_manager->GetAllDownloads(&all_items);
-    }
+    original_download_item_notifier_->GetManager()->GetAllDownloads(&all_items);
   }
   return all_items;
 }
diff --git a/chrome/browser/download/bubble/download_bubble_update_service.h b/chrome/browser/download/bubble/download_bubble_update_service.h
index 4c3c0bc..5c3e8a0c 100644
--- a/chrome/browser/download/bubble/download_bubble_update_service.h
+++ b/chrome/browser/download/bubble/download_bubble_update_service.h
@@ -97,6 +97,24 @@
   // anyway. Virtual for testing.
   virtual DownloadDisplayController::ProgressInfo GetProgressInfo() const;
 
+  // Initializes AllDownloadItemNotifier for the current profile, and
+  // initializes caches. This is called when the manager is ready, to signal
+  // that the DownloadBubbleUpdateService should begin tracking downloads. This
+  // starts initialization of both the download items and the offline items.
+  // Should only be called once.
+  void Initialize(content::DownloadManager* manager);
+
+  // Initializes the AllDownloadItemNotifier for the original profile, if
+  // |profile_| is off the record. May trigger re-initialization of the download
+  // items cache.
+  void InitializeOriginalNotifier(content::DownloadManager* manager);
+
+  // Get the DownloadManager that |download_item_notifier_| is listening to.
+  content::DownloadManager* GetDownloadManager();
+
+  // Virtual for testing.
+  virtual bool IsInitialized() const;
+
   // KeyedService:
   void Shutdown() override;
 
@@ -130,7 +148,12 @@
   }
 
   download::AllDownloadItemNotifier& download_item_notifier_for_testing() {
-    return download_item_notifier_;
+    return *download_item_notifier_;
+  }
+
+  download::AllDownloadItemNotifier&
+  original_download_item_notifier_for_testing() {
+    return *original_download_item_notifier_;
   }
 
  private:
@@ -278,18 +301,19 @@
   DownloadItemIterMap download_items_iter_map_;
   OfflineItemIterMap offline_items_iter_map_;
 
-  // Notifier for the current profile's DownloadManager.
-  download::AllDownloadItemNotifier download_item_notifier_;
-  // Nullopt if the profile is not OTR. If the profile is OTR, this holds a
-  // notifier for the original profile.
-  absl::optional<download::AllDownloadItemNotifier>
+  // Notifier for the current profile's DownloadManager. Null until initialized
+  // in Initialize().
+  std::unique_ptr<download::AllDownloadItemNotifier> download_item_notifier_;
+  // Null if the profile is not OTR. Null until the original profile initiates
+  // a download. If the profile is OTR, this holds a notifier for the original
+  // profile.
+  std::unique_ptr<download::AllDownloadItemNotifier>
       original_download_item_notifier_;
 
   bool offline_items_initialized_ = false;
   // Holds functions queued up while offline items were being initialized.
   std::vector<base::OnceClosure> offline_item_callbacks_;
 
-  bool download_manager_shut_down_ = false;
   bool offline_content_provider_shut_down_ = false;
 
   // Set of GUIDs for extension/theme (crx) downloads that are pending notifying
diff --git a/chrome/browser/download/bubble/download_bubble_update_service_factory.cc b/chrome/browser/download/bubble/download_bubble_update_service_factory.cc
index 12936b9..7a08fe4 100644
--- a/chrome/browser/download/bubble/download_bubble_update_service_factory.cc
+++ b/chrome/browser/download/bubble/download_bubble_update_service_factory.cc
@@ -28,7 +28,12 @@
 DownloadBubbleUpdateServiceFactory::DownloadBubbleUpdateServiceFactory()
     : ProfileKeyedServiceFactory(
           "DownloadBubbleUpdateService",
-          ProfileSelections::BuildForRegularAndIncognito()) {
+          ProfileSelections::Builder()
+              .WithRegular(ProfileSelection::kOwnInstance)
+              .WithGuest(ProfileSelection::kOffTheRecordOnly)
+              .WithSystem(ProfileSelection::kNone)
+              .WithAshInternals(ProfileSelection::kNone)
+              .Build()) {
   DependsOn(OfflineContentAggregatorFactory::GetInstance());
   DependsOn(OfflineItemModelManagerFactory::GetInstance());
 }
diff --git a/chrome/browser/download/bubble/download_bubble_update_service_unittest.cc b/chrome/browser/download/bubble/download_bubble_update_service_unittest.cc
index e04c68a..000c713 100644
--- a/chrome/browser/download/bubble/download_bubble_update_service_unittest.cc
+++ b/chrome/browser/download/bubble/download_bubble_update_service_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
+#include "chrome/browser/download/bubble/download_bubble_update_service_factory.h"
 #include "chrome/browser/download/bubble/download_display_controller.h"
 #include "chrome/browser/download/download_ui_model.h"
 #include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
@@ -37,7 +38,7 @@
 using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::ReturnRefOfCopy;
-using ::testing::SetArgPointee;
+using ::testing::WithArg;
 using DownloadState = download::DownloadItem::DownloadState;
 using DownloadUIModelPtrVector =
     std::vector<DownloadUIModel::DownloadUIModelPtr>;
@@ -46,6 +47,23 @@
 constexpr char kProfileName[] = "testing_profile";
 constexpr char kProviderNamespace[] = "mock_namespace";
 
+// Helper for MockDownloadManager to properly mimic GetAllDownloads(), such that
+// it appends all items in |items| to the argument of the action, which should
+// be a std::vector<DownloadItem*>.
+ACTION_P(AddDownloadItems, items) {
+  for (download::DownloadItem* item : items) {
+    arg0->push_back(item);
+  }
+}
+
+void RemoveDownloadItemsObserver(
+    std::vector<std::unique_ptr<NiceMockDownloadItem>>& download_items,
+    download::DownloadItem::Observer& observer) {
+  for (auto& item : download_items) {
+    item->RemoveObserver(&observer);
+  }
+}
+
 class DownloadBubbleUpdateServiceTest : public testing::Test {
  public:
   DownloadBubbleUpdateServiceTest()
@@ -59,36 +77,58 @@
       const DownloadBubbleUpdateServiceTest&) = delete;
   ~DownloadBubbleUpdateServiceTest() override = default;
 
+  raw_ptr<NiceMock<content::MockDownloadManager>> SetUpDownloadManager(
+      Profile* profile) {
+    auto download_manager =
+        std::make_unique<NiceMock<content::MockDownloadManager>>();
+    raw_ptr<NiceMock<content::MockDownloadManager>> manager =
+        download_manager.get();
+    EXPECT_CALL(*download_manager, GetBrowserContext())
+        .WillRepeatedly(Return(profile));
+    EXPECT_CALL(*download_manager, RemoveObserver(_)).WillRepeatedly(Return());
+    profile->SetDownloadManagerForTesting(std::move(download_manager));
+    return manager;
+  }
+
+  // Pass a null |download_manager| to avoid registering the download manager.
+  std::unique_ptr<KeyedService> InitUpdateService(
+      NiceMock<content::MockDownloadManager>* download_manager,
+      DownloadBubbleUpdateService* update_service,
+      content::BrowserContext* context) {
+    // Unregister the observer from the previous instance of the update service.
+    // See note below in TearDown() for why this is necessary.
+    if (update_service) {
+      RemoveDownloadItemsObserver(
+          download_items_,
+          update_service->download_item_notifier_for_testing());
+    }
+    auto service = std::make_unique<DownloadBubbleUpdateService>(
+        Profile::FromBrowserContext(context));
+    if (download_manager) {
+      service->Initialize(download_manager);
+    }
+    task_environment_.RunUntilIdle();
+    return service;
+  }
+
   void SetUp() override {
     ASSERT_TRUE(testing_profile_manager_.SetUp());
     profile_ = testing_profile_manager_.CreateTestingProfile(kProfileName);
-    auto download_manager =
-        std::make_unique<NiceMock<content::MockDownloadManager>>();
-    download_manager_ = download_manager.get();
-    EXPECT_CALL(*download_manager, GetBrowserContext())
-        .WillRepeatedly(Return(profile_.get()));
-    EXPECT_CALL(*download_manager, RemoveObserver(_)).WillRepeatedly(Return());
-    profile_->SetDownloadManagerForTesting(std::move(download_manager));
+    download_manager_ = SetUpDownloadManager(profile_);
 
     offline_content_provider_ = std::make_unique<
         NiceMock<offline_items_collection::MockOfflineContentProvider>>();
     OfflineContentAggregatorFactory::GetForKey(profile_->GetProfileKey())
         ->RegisterProvider(kProviderNamespace, offline_content_provider_.get());
 
-    InitUpdateService();
-  }
-
-  void InitUpdateService() {
-    // Unregister the observer from the previous instance of the update service.
-    // See note below in TearDown() for why this is necessary.
-    if (update_service_) {
-      for (auto& item : download_items_) {
-        item->RemoveObserver(
-            &update_service_->download_item_notifier_for_testing());
-      }
-    }
-    update_service_ = std::make_unique<DownloadBubbleUpdateService>(profile_);
-    task_environment_.RunUntilIdle();
+    update_service_ = static_cast<DownloadBubbleUpdateService*>(
+        DownloadBubbleUpdateServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                profile_,
+                base::BindRepeating(
+                    &DownloadBubbleUpdateServiceTest::InitUpdateService,
+                    base::Unretained(this), download_manager_.get(),
+                    update_service_)));
   }
 
   void TearDown() override {
@@ -96,14 +136,12 @@
     // notifier doesn't know what items it's observing because we have manually
     // added it as an observer on each item, since the MockDownloadManager does
     // not actually call OnDownloadCreated on it.
-    for (auto& item : download_items_) {
-      item->RemoveObserver(
-          &update_service_->download_item_notifier_for_testing());
+    if (update_service_) {
+      RemoveDownloadItemsObserver(
+          download_items_,
+          update_service_->download_item_notifier_for_testing());
     }
-    update_service_.reset();
     offline_content_provider_.reset();
-    download_manager_ = nullptr;
-    profile_ = nullptr;
     testing_profile_manager_.DeleteTestingProfile(kProfileName);
   }
 
@@ -113,18 +151,35 @@
     return *download_items_[index];
   }
 
+  // Forwards to the below version.
   void InitDownloadItem(DownloadState state,
                         const std::string& guid,
                         bool is_paused,
                         base::Time start_time = base::Time::Now(),
                         bool is_crx = false,
                         bool observe = true) {
-    size_t index = download_items_.size();
-    download_items_.push_back(std::make_unique<NiceMockDownloadItem>());
-    auto& item = GetDownloadItem(index);
+    InitDownloadItem(*download_manager_, *update_service_, download_items_,
+                     profile_, state, guid, is_paused, start_time, is_crx,
+                     observe);
+  }
+
+  void InitDownloadItem(
+      NiceMock<content::MockDownloadManager>& download_manager,
+      DownloadBubbleUpdateService& update_service,
+      std::vector<std::unique_ptr<NiceMockDownloadItem>>& download_items,
+      Profile* profile,
+      DownloadState state,
+      const std::string& guid,
+      bool is_paused,
+      base::Time start_time = base::Time::Now(),
+      bool is_crx = false,
+      bool observe = true) {
+    size_t index = download_items.size();
+    download_items.push_back(std::make_unique<NiceMockDownloadItem>());
+    auto& item = *download_items[index];
     EXPECT_CALL(item, GetId())
         .WillRepeatedly(
-            Return(static_cast<uint32_t>(download_items_.size() + 1)));
+            Return(static_cast<uint32_t>(download_items.size() + 1)));
     EXPECT_CALL(item, GetGuid()).WillRepeatedly(ReturnRefOfCopy(guid));
     EXPECT_CALL(item, GetState()).WillRepeatedly(Return(state));
     EXPECT_CALL(item, GetStartTime()).WillRepeatedly(Return(start_time));
@@ -146,16 +201,16 @@
     EXPECT_CALL(item, GetMimeType())
         .WillRepeatedly(Return(is_crx ? "application/x-chrome-extension" : ""));
     std::vector<download::DownloadItem*> items;
-    for (size_t i = 0; i < download_items_.size(); ++i) {
-      items.push_back(&GetDownloadItem(i));
+    for (auto& download_item : download_items) {
+      items.push_back(download_item.get());
     }
-    EXPECT_CALL(*download_manager_, GetAllDownloads(_))
-        .WillRepeatedly(SetArgPointee<0>(items));
-    EXPECT_CALL(*download_manager_, GetDownloadByGuid(guid))
+    EXPECT_CALL(download_manager, GetAllDownloads(_))
+        .WillRepeatedly(WithArg<0>(AddDownloadItems(items)));
+    EXPECT_CALL(download_manager, GetDownloadByGuid(guid))
         .WillRepeatedly(Return(&item));
-    content::DownloadItemUtils::AttachInfoForTesting(&item, profile_, nullptr);
+    content::DownloadItemUtils::AttachInfoForTesting(&item, profile, nullptr);
     if (observe) {
-      item.AddObserver(&update_service_->download_item_notifier_for_testing());
+      item.AddObserver(&update_service.download_item_notifier_for_testing());
       item.NotifyObserversDownloadUpdated();
     }
   }
@@ -189,7 +244,7 @@
       items.push_back(&GetDownloadItem(i));
     }
     EXPECT_CALL(*download_manager_, GetAllDownloads(_))
-        .WillRepeatedly(SetArgPointee<0>(items));
+        .WillRepeatedly(WithArg<0>(AddDownloadItems(items)));
   }
 
   void InitOfflineItems(const std::vector<OfflineItemState>& states,
@@ -220,15 +275,15 @@
   base::test::ScopedFeatureList feature_list_;
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  raw_ptr<NiceMock<content::MockDownloadManager>> download_manager_;
+  raw_ptr<NiceMock<content::MockDownloadManager>> download_manager_ = nullptr;
   std::vector<std::unique_ptr<NiceMockDownloadItem>> download_items_;
   std::vector<offline_items_collection::OfflineItem> offline_items_;
   TestingProfileManager testing_profile_manager_;
-  raw_ptr<TestingProfile> profile_;
+  raw_ptr<TestingProfile> profile_ = nullptr;
   std::unique_ptr<
       NiceMock<offline_items_collection::MockOfflineContentProvider>>
       offline_content_provider_;
-  std::unique_ptr<DownloadBubbleUpdateService> update_service_;
+  raw_ptr<DownloadBubbleUpdateService> update_service_ = nullptr;
 };
 
 TEST_F(DownloadBubbleUpdateServiceTest, PopulatesCaches) {
@@ -250,7 +305,9 @@
 
   // Recreate the update service to check that it pulls in the existing
   // download items upon initialization.
-  InitUpdateService();
+  auto service =
+      InitUpdateService(download_manager_, update_service_, profile_);
+  update_service_ = static_cast<DownloadBubbleUpdateService*>(service.get());
 
   EXPECT_TRUE(update_service_->GetAllModelsToDisplay(models));
   ASSERT_EQ(models.size(), 3u);
@@ -275,6 +332,11 @@
   EXPECT_EQ(models[3]->GetContentId().id, "in_progress_paused_offline_item");
   EXPECT_EQ(models[4]->GetContentId().id, "completed_download");
   EXPECT_EQ(models[5]->GetContentId().id, "completed_offline_item");
+
+  // Manually clean up the second service instance to avoid UAF.
+  RemoveDownloadItemsObserver(
+      download_items_, update_service_->download_item_notifier_for_testing());
+  update_service_ = nullptr;
 }
 
 TEST_F(DownloadBubbleUpdateServiceTest, AddsNonCrxDownloadItems) {
@@ -608,4 +670,86 @@
   EXPECT_EQ(progress_info.progress_percentage, 50);
 }
 
+class DownloadBubbleUpdateServiceIncognitoTest
+    : public DownloadBubbleUpdateServiceTest {
+ public:
+  DownloadBubbleUpdateServiceIncognitoTest() = default;
+  DownloadBubbleUpdateServiceIncognitoTest(
+      const DownloadBubbleUpdateServiceIncognitoTest&) = delete;
+  DownloadBubbleUpdateServiceIncognitoTest& operator=(
+      const DownloadBubbleUpdateServiceIncognitoTest&) = delete;
+  ~DownloadBubbleUpdateServiceIncognitoTest() override = default;
+
+  void SetUp() override {
+    DownloadBubbleUpdateServiceTest::SetUp();
+
+    incognito_profile_ = profile_->GetOffTheRecordProfile(
+        Profile::OTRProfileID::CreateUniqueForTesting(),
+        /*create_if_needed=*/true);
+    incognito_download_manager_ = SetUpDownloadManager(incognito_profile_);
+    // Pass nullptr for the download_manager to delay RegisterDownloadManager()
+    // call for the test.
+    incognito_update_service_ = static_cast<DownloadBubbleUpdateService*>(
+        DownloadBubbleUpdateServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                incognito_profile_,
+                base::BindRepeating(
+                    &DownloadBubbleUpdateServiceTest::InitUpdateService,
+                    base::Unretained(this), /*download_manager=*/nullptr,
+                    incognito_update_service_)));
+  }
+
+  void TearDown() override {
+    RemoveDownloadItemsObserver(
+        incognito_download_items_,
+        incognito_update_service_->download_item_notifier_for_testing());
+    RemoveDownloadItemsObserver(
+        download_items_, incognito_update_service_
+                             ->original_download_item_notifier_for_testing());
+    DownloadBubbleUpdateServiceTest::TearDown();
+  }
+
+ protected:
+  raw_ptr<Profile> incognito_profile_ = nullptr;
+  raw_ptr<NiceMock<content::MockDownloadManager>> incognito_download_manager_ =
+      nullptr;
+  std::vector<std::unique_ptr<NiceMockDownloadItem>> incognito_download_items_;
+  raw_ptr<DownloadBubbleUpdateService> incognito_update_service_ = nullptr;
+};
+
+// Tests that initializing an update service for an incognito profile sets both
+// the download manager and the original download manager.
+TEST_F(DownloadBubbleUpdateServiceIncognitoTest, InitIncognito) {
+  base::Time now = base::Time::Now();
+  // |observe| is false because this only tests initialization.
+  InitDownloadItem(DownloadState::COMPLETE, "regular_profile_download",
+                   /*is_paused=*/false, now - base::Hours(1), /*is_crx=*/false,
+                   /*observe=*/false);
+  InitDownloadItem(*incognito_download_manager_, *incognito_update_service_,
+                   incognito_download_items_, incognito_profile_,
+                   DownloadState::COMPLETE, "incognito_profile_download",
+                   /*is_paused=*/false, now, /*is_crx=*/false,
+                   /*observe=*/false);
+
+  // Initial state: Regular profile's update service has a manager, incognito's
+  // does not.
+  EXPECT_NE(update_service_->GetDownloadManager(), nullptr);
+  EXPECT_EQ(update_service_->GetDownloadManager(), download_manager_);
+  EXPECT_EQ(incognito_update_service_->GetDownloadManager(), nullptr);
+
+  incognito_update_service_->Initialize(incognito_download_manager_);
+  // Regular profile's update service's manager hasn't changed.
+  EXPECT_EQ(update_service_->GetDownloadManager(), download_manager_);
+  // Incognito profile's update service's manager now set correctly.
+  EXPECT_EQ(incognito_update_service_->GetDownloadManager(),
+            incognito_download_manager_);
+
+  // Both download items should be present after initialization.
+  DownloadUIModelPtrVector models;
+  EXPECT_TRUE(incognito_update_service_->GetAllModelsToDisplay(models));
+  ASSERT_EQ(models.size(), 2u);
+  EXPECT_EQ(models[0]->GetContentId().id, "incognito_profile_download");
+  EXPECT_EQ(models[1]->GetContentId().id, "regular_profile_download");
+}
+
 }  // namespace
diff --git a/chrome/browser/download/bubble/download_display_controller.cc b/chrome/browser/download/bubble/download_display_controller.cc
index df5aa901..b1c4c58 100644
--- a/chrome/browser/download/bubble/download_display_controller.cc
+++ b/chrome/browser/download/bubble/download_display_controller.cc
@@ -96,7 +96,6 @@
     DownloadBubbleUIController* bubble_controller)
     : display_(display),
       browser_(browser),
-      download_manager_(browser_->profile()->GetDownloadManager()),
       bubble_controller_(bubble_controller) {
   bubble_controller_->SetDownloadDisplayController(this);
   // |display| can be null in tests.
@@ -281,8 +280,12 @@
 std::vector<DownloadUIModelPtr>
 DownloadDisplayController::UpdateButtonStateFromAllModels(bool may_retry) {
   std::vector<std::unique_ptr<DownloadUIModel>> all_models;
-  bool results_complete =
-      bubble_controller_->update_service()->GetAllModelsToDisplay(all_models);
+  DownloadBubbleUpdateService* update_service =
+      bubble_controller_->update_service();
+  if (!update_service->IsInitialized()) {
+    return all_models;
+  }
+  bool results_complete = update_service->GetAllModelsToDisplay(all_models);
   UpdateToolbarButtonState(all_models);
   if (!results_complete && may_retry) {
     base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
@@ -312,7 +315,7 @@
 
 base::Time DownloadDisplayController::GetLastCompleteTime(
     const std::vector<std::unique_ptr<DownloadUIModel>>& all_models) {
-  base::Time last_time = DownloadPrefs::FromDownloadManager(download_manager_)
+  base::Time last_time = DownloadPrefs::FromBrowserContext(browser_->profile())
                              ->GetLastCompleteTime();
   for (const auto& model : all_models) {
     if (last_time < model->GetEndTime()) {
diff --git a/chrome/browser/download/bubble/download_display_controller.h b/chrome/browser/download/bubble/download_display_controller.h
index 91ab749..8df3d37 100644
--- a/chrome/browser/download/bubble/download_display_controller.h
+++ b/chrome/browser/download/bubble/download_display_controller.h
@@ -15,10 +15,6 @@
 #include "components/offline_items_collection/core/offline_content_aggregator.h"
 #include "components/offline_items_collection/core/offline_content_provider.h"
 
-namespace content {
-class DownloadManager;
-}  // namespace content
-
 class DownloadBubbleUIController;
 
 namespace base {
@@ -111,10 +107,6 @@
   // Returns the DownloadDisplay. Should always return a valid display.
   DownloadDisplay* download_display_for_testing() { return display_; }
 
-  void set_manager_for_testing(content::DownloadManager* manager) {
-    download_manager_ = manager;
-  }
-
  private:
   friend class DownloadDisplayControllerTest;
 
@@ -137,8 +129,7 @@
   // button is already showing.
   void ShowToolbarButton();
 
-  // Based on the information from `download_manager_`, updates the icon state
-  // of the `display_`.
+  // Updates the icon state of the `display_`.
   void UpdateToolbarButtonState(
       std::vector<std::unique_ptr<DownloadUIModel>>& all_models);
   // Asks `display_` to make the download icon inactive.
@@ -158,7 +149,6 @@
   raw_ptr<Browser> browser_;
   base::ScopedObservation<FullscreenController, FullscreenObserver>
       observation_{this};
-  raw_ptr<content::DownloadManager, DanglingUntriaged> download_manager_;
   base::OneShotTimer icon_disappearance_timer_;
   base::OneShotTimer icon_inactive_timer_;
   IconInfo icon_info_;
diff --git a/chrome/browser/download/bubble/download_display_controller_unittest.cc b/chrome/browser/download/bubble/download_display_controller_unittest.cc
index 183512bc..3bb721fa 100644
--- a/chrome/browser/download/bubble/download_display_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_display_controller_unittest.cc
@@ -41,7 +41,6 @@
 using ::testing::Return;
 using ::testing::ReturnRef;
 using ::testing::ReturnRefOfCopy;
-using ::testing::SetArgPointee;
 using ::testing::StrictMock;
 using StrictMockDownloadItem = StrictMock<download::MockDownloadItem>;
 using DownloadIconState = download::DownloadIconState;
@@ -169,6 +168,8 @@
     }
   }
 
+  bool IsInitialized() const override { return true; }
+
   MOCK_METHOD(DownloadDisplayController::ProgressInfo,
               GetProgressInfo,
               (),
@@ -207,8 +208,7 @@
 class DownloadDisplayControllerTest : public testing::Test {
  public:
   DownloadDisplayControllerTest()
-      : manager_(std::make_unique<NiceMock<content::MockDownloadManager>>()),
-        testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {
+      : testing_profile_manager_(TestingBrowserProcess::GetGlobal()) {
     base::CommandLine::ForCurrentProcess()->AppendSwitch(switches::kNoFirstRun);
   }
   DownloadDisplayControllerTest(const DownloadDisplayControllerTest&) = delete;
@@ -219,8 +219,6 @@
     ASSERT_TRUE(testing_profile_manager_.SetUp());
 
     profile_ = testing_profile_manager_.CreateTestingProfile("testing_profile");
-    EXPECT_CALL(*manager_.get(), GetBrowserContext())
-        .WillRepeatedly(Return(profile_.get()));
 
     DownloadCoreServiceFactory::GetInstance()->SetTestingFactory(
         profile_, base::BindRepeating(&BuildMockDownloadCoreService));
@@ -243,10 +241,8 @@
     browser_ = std::unique_ptr<Browser>(Browser::Create(params));
     bubble_controller_ = std::make_unique<DownloadBubbleUIController>(
         browser_.get(), mock_update_service_.get());
-    bubble_controller_->set_manager_for_testing(manager_.get());
     controller_ = std::make_unique<DownloadDisplayController>(
         display_.get(), browser_.get(), bubble_controller_.get());
-    controller_->set_manager_for_testing(manager_.get());
     display_->SetController(controller_.get());
   }
 
@@ -260,7 +256,6 @@
   Browser* browser() { return browser_.get(); }
 
  protected:
-  NiceMock<content::MockDownloadManager>& manager() { return *manager_.get(); }
   download::MockDownloadItem& item(size_t index) { return *items_[index]; }
   FakeDownloadDisplay& display() { return *display_; }
   DownloadDisplayController& controller() { return *controller_; }
@@ -308,8 +303,6 @@
     if (state == DownloadState::IN_PROGRESS) {
       in_progress_count_++;
     }
-    EXPECT_CALL(manager(), InProgressCount())
-        .WillRepeatedly(Return(in_progress_count_));
     // Set actioned_on to false (it defaults to true) because the controller
     // will generally set this to false in OnNewItem().
     DownloadItemModel(&item(index)).SetActionedOn(false);
@@ -318,8 +311,6 @@
     for (size_t i = 0; i < items_.size(); ++i) {
       items.push_back(&item(i));
     }
-    EXPECT_CALL(*manager_.get(), GetAllDownloads(_))
-        .WillRepeatedly(SetArgPointee<0>(items));
     content::DownloadItemUtils::AttachInfoForTesting(&(item(index)), profile_,
                                                      nullptr);
     mock_update_service_->AddModel(
@@ -373,15 +364,13 @@
         .WillRepeatedly(Return(danger_type));
     if (state == DownloadState::COMPLETE) {
       EXPECT_CALL(item(item_index), IsDone()).WillRepeatedly(Return(true));
-      DownloadPrefs::FromDownloadManager(&manager())
-          ->SetLastCompleteTime(base::Time::Now());
+      DownloadPrefs::FromBrowserContext(profile())->SetLastCompleteTime(
+          base::Time::Now());
     } else {
       EXPECT_CALL(item(item_index), IsDone()).WillRepeatedly(Return(false));
     }
     if (state == DownloadState::COMPLETE || in_progress_dangerous) {
       in_progress_count_--;
-      EXPECT_CALL(manager(), InProgressCount())
-          .WillRepeatedly(Return(in_progress_count_));
     }
     controller().OnUpdatedItem(
         state == DownloadState::COMPLETE || in_progress_dangerous,
@@ -396,8 +385,6 @@
     for (size_t i = 0; i < items_.size(); ++i) {
       items.push_back(&item(i));
     }
-    EXPECT_CALL(*manager_.get(), GetAllDownloads(_))
-        .WillRepeatedly(SetArgPointee<0>(items));
     mock_update_service_->RemoveLastDownload();
   }
 
@@ -441,7 +428,6 @@
   std::unique_ptr<FakeDownloadDisplay> display_;
   std::vector<std::unique_ptr<StrictMockDownloadItem>> items_;
   std::vector<OfflineItem> offline_items_;
-  std::unique_ptr<NiceMock<content::MockDownloadManager>> manager_;
   std::unique_ptr<StrictMock<MockDownloadBubbleUpdateService>>
       mock_update_service_;
   std::unique_ptr<DownloadBubbleUIController> bubble_controller_;
@@ -833,8 +819,8 @@
                    download::DownloadItem::COMPLETE);
   base::Time current_time = base::Time::Now();
   // Set the last complete time to more than 1 hour ago.
-  DownloadPrefs::FromDownloadManager(&manager())
-      ->SetLastCompleteTime(current_time - base::Minutes(61));
+  DownloadPrefs::FromBrowserContext(profile())->SetLastCompleteTime(
+      current_time - base::Minutes(61));
 
   DownloadDisplayController controller(&display(), browser(),
                                        &bubble_controller());
@@ -848,8 +834,8 @@
                    download::DownloadItem::COMPLETE);
   base::Time current_time = base::Time::Now();
   // Set the last complete time to less than 1 hour ago.
-  DownloadPrefs::FromDownloadManager(&manager())
-      ->SetLastCompleteTime(current_time - base::Minutes(59));
+  DownloadPrefs::FromBrowserContext(profile())->SetLastCompleteTime(
+      current_time - base::Minutes(59));
 
   DownloadDisplayController controller(&display(), browser(),
                                        &bubble_controller());
@@ -883,8 +869,8 @@
        InitialState_NewLastDownloadWithEmptyItem) {
   base::Time current_time = base::Time::Now();
   // Set the last complete time to less than 1 hour ago.
-  DownloadPrefs::FromDownloadManager(&manager())
-      ->SetLastCompleteTime(current_time - base::Minutes(59));
+  DownloadPrefs::FromBrowserContext(profile())->SetLastCompleteTime(
+      current_time - base::Minutes(59));
 
   DownloadDisplayController controller(&display(), browser(),
                                        &bubble_controller());
diff --git a/chrome/browser/download/download_core_service_factory.cc b/chrome/browser/download/download_core_service_factory.cc
index eb4f33f7..1d3ea33 100644
--- a/chrome/browser/download/download_core_service_factory.cc
+++ b/chrome/browser/download/download_core_service_factory.cc
@@ -10,6 +10,10 @@
 #include "chrome/browser/offline_items_collection/offline_content_aggregator_factory.h"
 #include "chrome/browser/profiles/profile.h"
 
+#if !BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/download/bubble/download_bubble_update_service_factory.h"
+#endif  // !BUILDFLAG(IS_ANDROID)
+
 // static
 DownloadCoreService* DownloadCoreServiceFactory::GetForBrowserContext(
     content::BrowserContext* context) {
@@ -26,6 +30,9 @@
     : ProfileKeyedServiceFactory(
           "DownloadCoreService",
           ProfileSelections::BuildForRegularAndIncognito()) {
+#if !BUILDFLAG(IS_ANDROID)
+  DependsOn(DownloadBubbleUpdateServiceFactory::GetInstance());
+#endif  // !BUILDFLAG(IS_ANDROID)
   DependsOn(HistoryServiceFactory::GetInstance());
   DependsOn(NotificationDisplayServiceFactory::GetInstance());
   DependsOn(OfflineContentAggregatorFactory::GetInstance());
diff --git a/chrome/browser/download/download_ui_controller.cc b/chrome/browser/download/download_ui_controller.cc
index c21f771..8ea555d 100644
--- a/chrome/browser/download/download_ui_controller.cc
+++ b/chrome/browser/download/download_ui_controller.cc
@@ -32,6 +32,7 @@
 #else
 #include "chrome/browser/download/bubble/download_bubble_prefs.h"
 #include "chrome/browser/download/bubble/download_bubble_ui_controller.h"
+#include "chrome/browser/download/bubble/download_bubble_update_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -68,6 +69,16 @@
 
 #else  // BUILDFLAG(IS_ANDROID)
 
+void InitializeDownloadBubbleUpdateService(Profile* profile,
+                                           content::DownloadManager* manager) {
+  DownloadBubbleUpdateService* download_bubble_update_service =
+      DownloadBubbleUpdateServiceFactory::GetForProfile(profile);
+  if (!download_bubble_update_service) {
+    return;
+  }
+  download_bubble_update_service->Initialize(manager);
+}
+
 class DownloadShelfUIControllerDelegate
     : public DownloadUIController::Delegate {
  public:
@@ -191,24 +202,24 @@
     delegate_ = std::make_unique<AndroidUIControllerDelegate>();
 #elif BUILDFLAG(IS_CHROMEOS)
   if (!delegate_) {
-    if (download::IsDownloadBubbleEnabled(
-            Profile::FromBrowserContext(manager->GetBrowserContext()))) {
-      delegate_ = std::make_unique<DownloadBubbleUIControllerDelegate>(
-          Profile::FromBrowserContext(manager->GetBrowserContext()));
+    Profile* profile =
+        Profile::FromBrowserContext(manager->GetBrowserContext());
+    if (download::IsDownloadBubbleEnabled(profile)) {
+      delegate_ = std::make_unique<DownloadBubbleUIControllerDelegate>(profile);
+      InitializeDownloadBubbleUpdateService(profile, manager);
     } else {
-      delegate_ = std::make_unique<DownloadNotificationManager>(
-          Profile::FromBrowserContext(manager->GetBrowserContext()));
+      delegate_ = std::make_unique<DownloadNotificationManager>(profile);
     }
   }
 #else   // BUILDFLAG(IS_CHROMEOS)
   if (!delegate_) {
-    if (download::IsDownloadBubbleEnabled(
-            Profile::FromBrowserContext(manager->GetBrowserContext()))) {
-      delegate_ = std::make_unique<DownloadBubbleUIControllerDelegate>(
-          Profile::FromBrowserContext(manager->GetBrowserContext()));
+    Profile* profile =
+        Profile::FromBrowserContext(manager->GetBrowserContext());
+    if (download::IsDownloadBubbleEnabled(profile)) {
+      delegate_ = std::make_unique<DownloadBubbleUIControllerDelegate>(profile);
+      InitializeDownloadBubbleUpdateService(profile, manager);
     } else {
-      delegate_ = std::make_unique<DownloadShelfUIControllerDelegate>(
-          Profile::FromBrowserContext(manager->GetBrowserContext()));
+      delegate_ = std::make_unique<DownloadShelfUIControllerDelegate>(profile);
     }
   }
 #endif  // BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/enterprise/connectors/device_trust/signals/decorators/ash/ash_signals_decorator_browsertest.cc b/chrome/browser/enterprise/connectors/device_trust/signals/decorators/ash/ash_signals_decorator_browsertest.cc
index 2430822..99d674d 100644
--- a/chrome/browser/enterprise/connectors/device_trust/signals/decorators/ash/ash_signals_decorator_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/signals/decorators/ash/ash_signals_decorator_browsertest.cc
@@ -185,7 +185,7 @@
       ash::ProfileHelper::Get()->GetUserByProfile(profile);
   AshSignalsDecorator decorator(connector_, profile);
 
-  std::set<std::string> user_affiliation_ids;
+  base::flat_set<std::string> user_affiliation_ids;
   user_affiliation_ids.insert(kFakeAffilationID);
 
   ash::ChromeUserManager::Get()->SetUserAffiliation(user->GetAccountId(),
diff --git a/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h b/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h
index 5548dfc..a36a6f4 100644
--- a/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h
+++ b/chrome/browser/extensions/api/passwords_private/passwords_private_event_router.h
@@ -45,12 +45,6 @@
   void OnPasswordExceptionsListChanged(
       const std::vector<api::passwords_private::ExceptionEntry>& exceptions);
 
-  // Notifies listeners after fetching a plain-text password.
-  // |id| the id for the password entry being shown.
-  // |plaintext_password| The human-readable password.
-  void OnPlaintextPasswordFetched(int id,
-                                  const std::string& plaintext_password);
-
   // Notifies listeners after the passwords have been written to the export
   // destination.
   // |file_path| In case of successful export, this will describe the path
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index 16e14d6..55aeb88 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -48,6 +48,7 @@
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
 #include "components/search_engines/default_search_manager.h"
 #include "components/services/screen_ai/buildflags/buildflags.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/spellcheck/browser/pref_names.h"
 #include "components/supervised_user/core/common/pref_names.h"
 #include "components/translate/core/browser/translate_pref_names.h"
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
index bb89a476..42c3c1f 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckCoordinator.java
@@ -10,8 +10,6 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.LifecycleObserver;
 
-import org.chromium.chrome.browser.device_reauth.DeviceAuthRequester;
-import org.chromium.chrome.browser.device_reauth.ReauthenticatorBridge;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckChangePasswordHelper;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckIconHelper;
@@ -31,7 +29,6 @@
     private final PasswordCheckFragmentView mFragmentView;
     private final SettingsLauncher mSettingsLauncher;
     private final PasswordAccessReauthenticationHelper mReauthenticationHelper;
-    private final ReauthenticatorBridge mReauthenticatorBridge;
     private final PasswordCheckMediator mMediator;
     private PropertyModel mModel;
 
@@ -81,9 +78,6 @@
 
         mReauthenticationHelper = new PasswordAccessReauthenticationHelper(
                 mFragmentView.getActivity(), mFragmentView.getParentFragmentManager());
-        // TODO(crbug.com/1434876): Clean up the ReauthenticatorBridge usage.
-        mReauthenticatorBridge =
-                ReauthenticatorBridge.create(DeviceAuthRequester.PASSWORD_CHECK_AUTO_PWD_CHANGE);
 
         PasswordCheckChangePasswordHelper changePasswordHelper =
                 new PasswordCheckChangePasswordHelper(mFragmentView.getActivity(),
@@ -91,8 +85,8 @@
         PasswordCheckIconHelper iconHelper = new PasswordCheckIconHelper(
                 new LargeIconBridge(Profile.getLastUsedRegularProfile()),
                 mFragmentView.getResources().getDimensionPixelSize(R.dimen.default_favicon_size));
-        mMediator = new PasswordCheckMediator(changePasswordHelper, mReauthenticationHelper,
-                mReauthenticatorBridge, mSettingsLauncher, iconHelper);
+        mMediator = new PasswordCheckMediator(
+                changePasswordHelper, mReauthenticationHelper, mSettingsLauncher, iconHelper);
     }
 
     private void launchCheckupInAccount() {
diff --git a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
index 901536c8c..e9461e5 100644
--- a/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
+++ b/chrome/browser/password_check/android/internal/java/src/org/chromium/chrome/browser/password_check/PasswordCheckMediator.java
@@ -31,7 +31,6 @@
 
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
-import org.chromium.chrome.browser.device_reauth.ReauthenticatorBridge;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckChangePasswordHelper;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckIconHelper;
 import org.chromium.chrome.browser.password_manager.PasswordCheckReferrer;
@@ -56,7 +55,6 @@
     private static long sStatusUpdateDelayMillis = 1000;
 
     private final PasswordAccessReauthenticationHelper mReauthenticationHelper;
-    private final ReauthenticatorBridge mReauthenticatorBridge;
     private final PasswordCheckChangePasswordHelper mChangePasswordDelegate;
     private PropertyModel mModel;
     private PasswordCheckComponentUi.Delegate mDelegate;
@@ -69,13 +67,11 @@
 
     PasswordCheckMediator(PasswordCheckChangePasswordHelper changePasswordDelegate,
             PasswordAccessReauthenticationHelper reauthenticationHelper,
-            ReauthenticatorBridge reauthenticatorBridge, SettingsLauncher settingsLauncher,
-            PasswordCheckIconHelper passwordCheckIconHelper) {
+            SettingsLauncher settingsLauncher, PasswordCheckIconHelper passwordCheckIconHelper) {
         mChangePasswordDelegate = changePasswordDelegate;
         mReauthenticationHelper = reauthenticationHelper;
         mSettingsLauncher = settingsLauncher;
         mIconHelper = passwordCheckIconHelper;
-        mReauthenticatorBridge = reauthenticatorBridge;
     }
 
     void initialize(PropertyModel model, PasswordCheckComponentUi.Delegate delegate,
diff --git a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
index 48b4c38..10beffd3 100644
--- a/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
+++ b/chrome/browser/password_check/android/junit/src/org/chromium/chrome/browser/password_check/PasswordCheckControllerTest.java
@@ -57,7 +57,6 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.UmaRecorderHolder;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.device_reauth.ReauthenticatorBridge;
 import org.chromium.chrome.browser.password_check.PasswordCheckProperties.ItemType;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckChangePasswordHelper;
 import org.chromium.chrome.browser.password_check.helper.PasswordCheckIconHelper;
@@ -110,8 +109,6 @@
     @Mock
     private PasswordAccessReauthenticationHelper mReauthenticationHelper;
     @Mock
-    private ReauthenticatorBridge mReauthenticatorBridge;
-    @Mock
     private SettingsLauncher mSettingsLauncher;
     @Mock
     private PasswordCheckIconHelper mIconHelper;
@@ -127,8 +124,8 @@
         UmaRecorderHolder.resetForTesting();
         MockitoAnnotations.initMocks(this);
         mModel = PasswordCheckProperties.createDefaultModel();
-        mMediator = new PasswordCheckMediator(mChangePasswordDelegate, mReauthenticationHelper,
-                mReauthenticatorBridge, mSettingsLauncher, mIconHelper);
+        mMediator = new PasswordCheckMediator(
+                mChangePasswordDelegate, mReauthenticationHelper, mSettingsLauncher, mIconHelper);
         PasswordCheckFactory.setPasswordCheckForTesting(mPasswordCheck);
         mMediator.initialize(mModel, mDelegate, PasswordCheckReferrer.PASSWORD_SETTINGS, () -> {});
         PasswordCheckMediator.setStatusUpdateDelayMillis(0);
diff --git a/chrome/browser/password_manager/android/BUILD.gn b/chrome/browser/password_manager/android/BUILD.gn
index fdded86c..4a311f2 100644
--- a/chrome/browser/password_manager/android/BUILD.gn
+++ b/chrome/browser/password_manager/android/BUILD.gn
@@ -435,6 +435,7 @@
     "password_generation_controller_impl_unittest.cc",
     "password_manager_error_message_delegate_unittest.cc",
     "password_manager_settings_service_android_impl_unittest.cc",
+    "password_manager_ui_util_android_unittest.cc",
     "password_settings_updater_android_bridge_helper_impl_unittest.cc",
     "password_store_android_backend_bridge_helper_impl_unittest.cc",
     "password_store_android_backend_unittest.cc",
diff --git a/chrome/browser/password_manager/android/password_accessory_controller_impl.cc b/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
index f42a723..5420773 100644
--- a/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
+++ b/chrome/browser/password_manager/android/password_accessory_controller_impl.cc
@@ -278,29 +278,6 @@
           password_client, std::move(driver_supplier))));
 }
 
-// static
-bool PasswordAccessoryControllerImpl::ShouldAcceptFocusEvent(
-    content::WebContents* web_contents,
-    password_manager::ContentPasswordManagerDriver* driver,
-    FocusedFieldType focused_field_type) {
-  // Only react to focus events that are sent for the current focused frame.
-  // This is used to make sure that obsolette events that come in an unexpected
-  // order are not processed. Example: (Frame1, focus) -> (Frame2, focus) ->
-  // (Frame1, unfocus) would otherwise unset all the data set for Frame2, which
-  // would be wrong.
-  if (web_contents->GetFocusedFrame() &&
-      driver->render_frame_host() == web_contents->GetFocusedFrame())
-    return true;
-
-  // The one event that is accepted even if there is no focused frame is an
-  // "unfocus" event that resulted in all frames being unfocused. This can be
-  // used to reset the state of the accessory.
-  if (!web_contents->GetFocusedFrame() &&
-      focused_field_type == FocusedFieldType::kUnknown)
-    return true;
-  return false;
-}
-
 void PasswordAccessoryControllerImpl::OnOptionSelected(
     autofill::AccessoryAction selected_action) {
   if (selected_action == autofill::AccessoryAction::USE_OTHER_PASSWORD) {
diff --git a/chrome/browser/password_manager/android/password_accessory_controller_impl.h b/chrome/browser/password_manager/android/password_accessory_controller_impl.h
index 61919dc..57c0175 100644
--- a/chrome/browser/password_manager/android/password_accessory_controller_impl.h
+++ b/chrome/browser/password_manager/android/password_accessory_controller_impl.h
@@ -27,10 +27,6 @@
 #include "content/public/browser/web_contents_user_data.h"
 #include "url/gurl.h"
 
-namespace password_manager {
-class ContentPasswordManagerDriver;
-}  // namespace password_manager
-
 class ManualFillingController;
 class AllPasswordsBottomSheetController;
 
@@ -87,16 +83,6 @@
       password_manager::PasswordManagerClient* password_client,
       PasswordDriverSupplierForFocusedFrame driver_supplier);
 
-  // True if the focus event was sent for the current focused frame or if it is
-  // a blur event and no frame is focused. This check avoids reacting to
-  // obsolete events that arrived in an unexpected order.
-  // TODO(crbug.com/968162): Introduce the concept of active frame to the
-  // accessory controller and move this check in the controller.
-  static bool ShouldAcceptFocusEvent(
-      content::WebContents* web_contents,
-      password_manager::ContentPasswordManagerDriver* driver,
-      autofill::mojom::FocusedFieldType focused_field_type);
-
   // Returns true if the current site attached to `web_contents_` has a SECURE
   // security level.
   bool IsSecureSite() const;
diff --git a/chrome/browser/password_manager/android/password_generation_controller.h b/chrome/browser/password_manager/android/password_generation_controller.h
index eed681f8..7c3eb22 100644
--- a/chrome/browser/password_manager/android/password_generation_controller.h
+++ b/chrome/browser/password_manager/android/password_generation_controller.h
@@ -67,7 +67,8 @@
   // Notifies the UI that automatic password generation is available.
   // A button should be displayed in the accessory bar.
   virtual void OnAutomaticGenerationAvailable(
-      const password_manager::PasswordManagerDriver* target_frame_driver,
+      base::WeakPtr<password_manager::PasswordManagerDriver>
+          target_frame_driver,
       const autofill::password_generation::PasswordGenerationUIData& ui_data,
       gfx::RectF element_bounds_in_screen_space) = 0;
 
diff --git a/chrome/browser/password_manager/android/password_generation_controller_impl.cc b/chrome/browser/password_manager/android/password_generation_controller_impl.cc
index 41ba5feac..14c5ce1 100644
--- a/chrome/browser/password_manager/android/password_generation_controller_impl.cc
+++ b/chrome/browser/password_manager/android/password_generation_controller_impl.cc
@@ -80,13 +80,17 @@
 }
 
 void PasswordGenerationControllerImpl::OnAutomaticGenerationAvailable(
-    const password_manager::PasswordManagerDriver* target_frame_driver,
+    base::WeakPtr<password_manager::PasswordManagerDriver> target_frame_driver,
     const autofill::password_generation::PasswordGenerationUIData& ui_data,
     gfx::RectF element_bounds_in_screen_space) {
-  if (!IsActiveFrameDriver(target_frame_driver))
-    return;
-  DCHECK(!dialog_view_);
+  // We can't be sure that the active frame driver would be set in the
+  // FocusedInputChanged by now, because there is a race condition. The roots
+  // of the OnAutomaticGenerationAvailable and FocusedInputChanged calls are
+  // the same in the renderer. So we need to set it here too.
+  FocusedInputChanged(autofill::mojom::FocusedFieldType::kFillablePasswordField,
+                      std::move(target_frame_driver));
 
+  CHECK(!dialog_view_);
   active_frame_driver_->GetPasswordManager()
       ->SetGenerationElementAndTypeForForm(
           active_frame_driver_.get(), ui_data.form_data.unique_renderer_id,
@@ -125,6 +129,11 @@
     base::WeakPtr<password_manager::PasswordManagerDriver> driver) {
   TRACE_EVENT0("passwords",
                "PasswordGenerationControllerImpl::FocusedInputChanged");
+  // It's probably a duplicate notification.
+  if (IsActiveFrameDriver(driver.get()) &&
+      focused_field_type == FocusedFieldType::kFillablePasswordField) {
+    return;
+  }
   ResetState();
   if (focused_field_type == FocusedFieldType::kFillablePasswordField)
     active_frame_driver_ = std::move(driver);
diff --git a/chrome/browser/password_manager/android/password_generation_controller_impl.h b/chrome/browser/password_manager/android/password_generation_controller_impl.h
index 6a7e60ab..4ff1426 100644
--- a/chrome/browser/password_manager/android/password_generation_controller_impl.h
+++ b/chrome/browser/password_manager/android/password_generation_controller_impl.h
@@ -49,7 +49,8 @@
       autofill::mojom::FocusedFieldType focused_field_type,
       base::WeakPtr<password_manager::PasswordManagerDriver> driver) override;
   void OnAutomaticGenerationAvailable(
-      const password_manager::PasswordManagerDriver* target_frame_driver,
+      base::WeakPtr<password_manager::PasswordManagerDriver>
+          target_frame_driver,
       const autofill::password_generation::PasswordGenerationUIData& ui_data,
       gfx::RectF element_bounds_in_screen_space) override;
   void ShowManualGenerationDialog(
diff --git a/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc b/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
index 0fbaf35..eab63d56 100644
--- a/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
+++ b/chrome/browser/password_manager/android/password_generation_controller_impl_unittest.cc
@@ -40,6 +40,7 @@
 using password_manager::MockPasswordStoreInterface;
 using password_manager::PasswordForm;
 using testing::_;
+using testing::AtMost;
 using testing::ByMove;
 using testing::Eq;
 using testing::Mock;
@@ -171,6 +172,8 @@
         test_pwd_manager_client_.get());
     mock_password_manager_driver_ =
         std::make_unique<NiceMock<MockPasswordManagerDriver>>();
+    mock_another_password_manager_driver_ =
+        std::make_unique<NiceMock<MockPasswordManagerDriver>>();
 
     // TODO(crbug.com/969051): Remove once kAutofillKeyboardAccessory is
     // enabled.
@@ -183,6 +186,11 @@
         .WillByDefault(Return(password_manager_.get()));
     ON_CALL(*mock_password_manager_driver_, GetPasswordAutofillManager())
         .WillByDefault(Return(password_autofill_manager_.get()));
+    ON_CALL(*mock_another_password_manager_driver_, GetPasswordManager())
+        .WillByDefault(Return(password_manager_.get()));
+    ON_CALL(*mock_another_password_manager_driver_,
+            GetPasswordAutofillManager())
+        .WillByDefault(Return(password_autofill_manager_.get()));
 
     mock_generation_helper_ =
         std::make_unique<NiceMock<MockPasswordGenerationHelper>>(
@@ -219,6 +227,8 @@
 
   std::unique_ptr<NiceMock<MockPasswordManagerDriver>>
       mock_password_manager_driver_;
+  std::unique_ptr<NiceMock<MockPasswordManagerDriver>>
+      mock_another_password_manager_driver_;
   std::unique_ptr<NiceMock<MockPasswordGenerationHelper>>
       mock_generation_helper_;
   std::unique_ptr<NiceMock<MockPasswordGenerationDialogView>> mock_dialog_;
@@ -243,7 +253,7 @@
               OnAutomaticGenerationStatusChanged(true));
 
   controller()->OnAutomaticGenerationAvailable(
-      mock_password_manager_driver_.get(), GetTestGenerationUIData1(),
+      mock_password_manager_driver_->AsWeakPtr(), GetTestGenerationUIData1(),
       gfx::RectF(100, 20));
 
   ON_CALL(*mock_generation_helper_, GeneratePassword(_, _, _, _))
@@ -272,7 +282,7 @@
   EXPECT_CALL(mock_manual_filling_controller_,
               OnAutomaticGenerationStatusChanged(true));
   controller()->OnAutomaticGenerationAvailable(
-      mock_password_manager_driver_.get(), GetTestGenerationUIData1(),
+      mock_password_manager_driver_->AsWeakPtr(), GetTestGenerationUIData1(),
       gfx::RectF(100, 20));
 }
 
@@ -286,11 +296,12 @@
               OnAutomaticGenerationStatusChanged(true))
       .Times(2);
   controller()->OnAutomaticGenerationAvailable(
-      mock_password_manager_driver_.get(), GetTestGenerationUIData1(),
+      mock_password_manager_driver_->AsWeakPtr(), GetTestGenerationUIData1(),
       gfx::RectF(100, 20));
   PasswordGenerationUIData new_ui_data = GetTestGenerationUIData2();
   controller()->OnAutomaticGenerationAvailable(
-      mock_password_manager_driver_.get(), new_ui_data, gfx::RectF(100, 20));
+      mock_password_manager_driver_->AsWeakPtr(), new_ui_data,
+      gfx::RectF(100, 20));
 
   autofill::FormSignature form_signature =
       autofill::CalculateFormSignature(new_ui_data.form_data);
@@ -323,7 +334,7 @@
   EXPECT_CALL(mock_manual_filling_controller_,
               OnAutomaticGenerationStatusChanged(true));
   controller()->OnAutomaticGenerationAvailable(
-      mock_password_manager_driver_.get(), GetTestGenerationUIData1(),
+      mock_password_manager_driver_->AsWeakPtr(), GetTestGenerationUIData1(),
       gfx::RectF(100, 20));
 
   EXPECT_CALL(mock_manual_filling_controller_,
@@ -387,13 +398,18 @@
 }
 
 TEST_F(PasswordGenerationControllerTest,
-       RejectAutomaticAvailableForNonActiveFrame) {
+       SetActiveFrameOnAutomaticGenerationAvailable) {
+  // TODO(crbug.com/1421753): Refactor PasswordGenerationController so that
+  // OnAutomaticGenerationStatusChanged would be called only once. Right now
+  // it's called twice: the first call resets the manual filling controller
+  // status and the second one sets it according to the focused input.
   EXPECT_CALL(mock_manual_filling_controller_,
               OnAutomaticGenerationStatusChanged(_))
-      .Times(0);
-  MockPasswordManagerDriver wrong_driver;
+      .Times(AtMost(2));
+
   controller()->OnAutomaticGenerationAvailable(
-      &wrong_driver, GetTestGenerationUIData2(), gfx::RectF(100, 20));
+      mock_another_password_manager_driver_->AsWeakPtr(),
+      GetTestGenerationUIData2(), gfx::RectF(100, 20));
 }
 
 TEST_F(PasswordGenerationControllerTest,
@@ -438,7 +454,7 @@
   EXPECT_CALL(*raw_dialog_view, Destroy());
   controller()->FocusedInputChanged(
       FocusedFieldType::kFillableUsernameField,
-      base::AsWeakPtr(mock_password_manager_driver_.get()));
+      mock_another_password_manager_driver_->AsWeakPtr());
   Mock::VerifyAndClearExpectations(raw_dialog_view);
 }
 
@@ -497,7 +513,7 @@
               OnAutomaticGenerationStatusChanged(false));
   controller()->FocusedInputChanged(
       FocusedFieldType::kFillablePasswordField,
-      base::AsWeakPtr(mock_password_manager_driver_.get()));
+      mock_another_password_manager_driver_->AsWeakPtr());
   EXPECT_CALL(mock_dialog_factory(), Run).Times(0);
   controller()->ShowManualGenerationDialog(mock_password_manager_driver_.get(),
                                            GetTestGenerationUIData1());
diff --git a/chrome/browser/password_manager/android/password_manager_ui_util_android.cc b/chrome/browser/password_manager/android/password_manager_ui_util_android.cc
new file mode 100644
index 0000000..6dfc20f
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_manager_ui_util_android.cc
@@ -0,0 +1,33 @@
+// 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/password_manager/android/password_manager_ui_util_android.h"
+
+#include "components/password_manager/content/browser/content_password_manager_driver.h"
+
+using autofill::mojom::FocusedFieldType;
+
+bool ShouldAcceptFocusEvent(
+    content::WebContents* web_contents,
+    password_manager::ContentPasswordManagerDriver* driver,
+    FocusedFieldType focused_field_type) {
+  // Only react to focus events that are sent for the current focused frame.
+  // This is used to make sure that obsolete events that come in an unexpected
+  // order are not processed. Example: (Frame1, focus) -> (Frame2, focus) ->
+  // (Frame1, unfocus) would otherwise unset all the data set for Frame2, which
+  // would be wrong.
+  if (web_contents->GetFocusedFrame() &&
+      driver->render_frame_host() == web_contents->GetFocusedFrame()) {
+    return true;
+  }
+
+  // The only event that is accepted even if there is no focused frame is an
+  // "unfocus" event that resulted in all frames being unfocused. This can be
+  // used to reset the focused state.
+  if (!web_contents->GetFocusedFrame() &&
+      focused_field_type == FocusedFieldType::kUnknown) {
+    return true;
+  }
+  return false;
+}
diff --git a/chrome/browser/password_manager/android/password_manager_ui_util_android.h b/chrome/browser/password_manager/android/password_manager_ui_util_android.h
new file mode 100644
index 0000000..97eed7c2
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_manager_ui_util_android.h
@@ -0,0 +1,19 @@
+// 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_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_UI_UTIL_ANDROID_H_
+#define CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_UI_UTIL_ANDROID_H_
+
+#include "components/password_manager/content/browser/content_password_manager_driver.h"
+#include "content/public/browser/web_contents.h"
+
+// True if the focus event was sent for the current focused frame or if it is
+// a blur event and no frame is focused. This check avoids reacting to
+// obsolete events that arrived in an unexpected order.
+bool ShouldAcceptFocusEvent(
+    content::WebContents* web_contents,
+    password_manager::ContentPasswordManagerDriver* driver,
+    autofill::mojom::FocusedFieldType focused_field_type);
+
+#endif  // CHROME_BROWSER_PASSWORD_MANAGER_ANDROID_PASSWORD_MANAGER_UI_UTIL_ANDROID_H_
diff --git a/chrome/browser/password_manager/android/password_manager_ui_util_android_unittest.cc b/chrome/browser/password_manager/android/password_manager_ui_util_android_unittest.cc
new file mode 100644
index 0000000..8fc1df37
--- /dev/null
+++ b/chrome/browser/password_manager/android/password_manager_ui_util_android_unittest.cc
@@ -0,0 +1,75 @@
+// 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/password_manager/android/password_manager_ui_util_android.h"
+
+#include <memory>
+
+#include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/autofill/core/browser/test_autofill_client.h"
+#include "components/password_manager/content/browser/content_password_manager_driver.h"
+#include "components/password_manager/core/browser/stub_password_manager_client.h"
+#include "content/public/browser/render_frame_host.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using autofill::mojom::FocusedFieldType;
+
+constexpr char kExampleSite[] = "https://example.com";
+
+class PasswordManagerUIUtilAndroidTest
+    : public ChromeRenderViewHostTestHarness {
+ public:
+  void SetUp() override {
+    ChromeRenderViewHostTestHarness::SetUp();
+    NavigateAndCommit(GURL(kExampleSite));
+    FocusWebContentsOnMainFrame();
+
+    ASSERT_TRUE(web_contents()->GetFocusedFrame());
+
+    password_mananger_driver_ = CreatePasswordManagerDriver(main_rfh());
+  }
+
+  password_manager::ContentPasswordManagerDriver* password_mananger_driver() {
+    return password_mananger_driver_.get();
+  }
+
+  std::unique_ptr<password_manager::ContentPasswordManagerDriver>
+  CreatePasswordManagerDriver(content::RenderFrameHost* rfh) {
+    return std::make_unique<password_manager::ContentPasswordManagerDriver>(
+        rfh, &client_, &test_autofill_client_);
+  }
+
+ private:
+  std::unique_ptr<password_manager::ContentPasswordManagerDriver>
+      password_mananger_driver_;
+  password_manager::StubPasswordManagerClient client_;
+  autofill::TestAutofillClient test_autofill_client_;
+};
+
+TEST_F(PasswordManagerUIUtilAndroidTest, ShouldAcceptFocusEvent) {
+  EXPECT_TRUE(ShouldAcceptFocusEvent(web_contents(), password_mananger_driver(),
+                                     FocusedFieldType::kFillablePasswordField));
+}
+
+TEST_F(PasswordManagerUIUtilAndroidTest,
+       ShouldNotAcceptFocusEventAfterFrameLostFocus) {
+  // Make a driver for another frame than the focused one.
+  content::RenderFrameHost* subframe =
+      content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe");
+  std::unique_ptr<password_manager::ContentPasswordManagerDriver> bad_driver =
+      CreatePasswordManagerDriver(subframe);
+
+  EXPECT_FALSE(
+      ShouldAcceptFocusEvent(web_contents(), bad_driver.get(),
+                             FocusedFieldType::kFillablePasswordField));
+}
+
+TEST_F(PasswordManagerUIUtilAndroidTest,
+       ShouldStillAcceptFocusEventIfNoFocusedFrame) {
+  // Reset contents, so that no frame was focused.
+  SetContents(CreateTestWebContents());
+
+  EXPECT_TRUE(ShouldAcceptFocusEvent(web_contents(), password_mananger_driver(),
+                                     FocusedFieldType::kUnknown));
+}
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index e746535b..2d5befc 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -134,6 +134,7 @@
 #include "chrome/browser/password_manager/android/password_accessory_controller_impl.h"
 #include "chrome/browser/password_manager/android/password_generation_controller.h"
 #include "chrome/browser/password_manager/android/password_manager_launcher_android.h"
+#include "chrome/browser/password_manager/android/password_manager_ui_util_android.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_controller.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_controller_autofill_delegate.h"
 #include "components/messages/android/messages_feature.h"
@@ -340,8 +341,8 @@
       ->NotifyFocusedInputChanged(focused_field_id, focused_field_type);
   password_manager::ContentPasswordManagerDriver* content_driver =
       static_cast<password_manager::ContentPasswordManagerDriver*>(driver);
-  if (!PasswordAccessoryControllerImpl::ShouldAcceptFocusEvent(
-          web_contents(), content_driver, focused_field_type)) {
+  if (!ShouldAcceptFocusEvent(web_contents(), content_driver,
+                              focused_field_type)) {
     return;
   }
 
@@ -995,29 +996,32 @@
   if (!password_manager::bad_message::CheckChildProcessSecurityPolicyForURL(
           rfh, ui_data.form_data.url,
           BadMessageReason::
-              CPMD_BAD_ORIGIN_AUTOMATIC_GENERATION_STATUS_CHANGED))
+              CPMD_BAD_ORIGIN_AUTOMATIC_GENERATION_STATUS_CHANGED)) {
     return;
+  }
 #if BUILDFLAG(IS_ANDROID)
-  PasswordManagerDriver* driver = driver_factory_->GetDriverForFrame(rfh);
+  password_manager::ContentPasswordManagerDriver* driver =
+      driver_factory_->GetDriverForFrame(rfh);
   // This method is called over Mojo via a RenderFrameHostReceiverSet; the
   // current target frame must be live.
   // TODO(crbug.com/1294378): Remove reference to nested frames once
   // EnablePasswordManagerWithinFencedFrame is launched.
-  DCHECK(driver || rfh->IsNestedWithinFencedFrame());
-  if (!driver) {
+  CHECK(driver || rfh->IsNestedWithinFencedFrame());
+  if (!driver ||
+      !ShouldAcceptFocusEvent(web_contents(), driver,
+                              FocusedFieldType::kFillablePasswordField)) {
     return;
   }
 
   PasswordGenerationController* generation_controller =
-      PasswordGenerationController::GetIfExisting(web_contents());
-  DCHECK(generation_controller);
+      PasswordGenerationController::GetOrCreate(web_contents());
 
   gfx::RectF element_bounds_in_screen_space = TransformToRootCoordinates(
       password_generation_driver_receivers_.GetCurrentTargetFrame(),
       ui_data.bounds);
 
   generation_controller->OnAutomaticGenerationAvailable(
-      driver, ui_data, element_bounds_in_screen_space);
+      driver->AsWeakPtr(), ui_data, element_bounds_in_screen_space);
 #else
   password_manager::ContentPasswordManagerDriver* driver =
       driver_factory_->GetDriverForFrame(rfh);
@@ -1025,7 +1029,7 @@
   // current target frame must be live.
   // TODO(crbug.com/1294378): Remove reference to nested frames once
   // EnablePasswordManagerWithinFencedFrame is launched.
-  DCHECK(driver || rfh->IsNestedWithinFencedFrame());
+  CHECK(driver || rfh->IsNestedWithinFencedFrame());
   if (!driver)
     return;
 
diff --git a/chrome/browser/privacy_guide/android/BUILD.gn b/chrome/browser/privacy_guide/android/BUILD.gn
index dec43515..fd859ff 100644
--- a/chrome/browser/privacy_guide/android/BUILD.gn
+++ b/chrome/browser/privacy_guide/android/BUILD.gn
@@ -125,6 +125,7 @@
     "//third_party/android_deps:espresso_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/hamcrest:hamcrest_core_java",
+    "//third_party/hamcrest:hamcrest_library_java",
     "//third_party/junit:junit",
     "//third_party/mockito:mockito_java",
     "//ui/android:ui_java_test_support",
@@ -148,6 +149,7 @@
     "java/res/layout/privacy_guide_explanation_item.xml",
     "java/res/layout/privacy_guide_history_sync_step.xml",
     "java/res/layout/privacy_guide_msbb_step.xml",
+    "java/res/layout/privacy_guide_sb_bottom_sheet_toolbar.xml",
     "java/res/layout/privacy_guide_sb_enhanced_explanation.xml",
     "java/res/layout/privacy_guide_sb_standard_explanation.xml",
     "java/res/layout/privacy_guide_sb_step.xml",
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_bottom_sheet_toolbar.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_bottom_sheet_toolbar.xml
new file mode 100644
index 0000000..da8eddb8
--- /dev/null
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_bottom_sheet_toolbar.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<org.chromium.ui.widget.OptimizedFrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:paddingVertical="8dp"
+        android:importantForAccessibility="no"
+        android:src="@drawable/drag_handlebar" />
+</org.chromium.ui.widget.OptimizedFrameLayout>
\ No newline at end of file
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_enhanced_explanation.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_enhanced_explanation.xml
index 94e9130..4b598d5 100644
--- a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_enhanced_explanation.xml
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_enhanced_explanation.xml
@@ -7,7 +7,8 @@
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:paddingTop="@dimen/sb_bottom_sheet_toolbar_height">
 
     <LinearLayout
         android:id="@+id/sb_enhanced_sheet"
@@ -81,7 +82,7 @@
         <!-- Extra empty space, necessary due to crbug.com/1287979. -->
         <View
             android:layout_width="match_parent"
-            android:layout_height="16dp"
+            android:layout_height="32dp"
             android:visibility="invisible" />
 
     </LinearLayout>
diff --git a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_standard_explanation.xml b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_standard_explanation.xml
index b8eae82..6a01bd76 100644
--- a/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_standard_explanation.xml
+++ b/chrome/browser/privacy_guide/android/java/res/layout/privacy_guide_sb_standard_explanation.xml
@@ -7,7 +7,8 @@
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content">
+    android:layout_height="wrap_content"
+    android:paddingTop="@dimen/sb_bottom_sheet_toolbar_height">
 
     <LinearLayout
         android:id="@+id/sb_standard_sheet"
diff --git a/chrome/browser/privacy_guide/android/java/res/values/dimens.xml b/chrome/browser/privacy_guide/android/java/res/values/dimens.xml
index e6ed6c70..11e1f83 100644
--- a/chrome/browser/privacy_guide/android/java/res/values/dimens.xml
+++ b/chrome/browser/privacy_guide/android/java/res/values/dimens.xml
@@ -13,4 +13,5 @@
   <dimen name="done_step_explanation_marginBottom">8dp</dimen>
   <dimen name="done_step_link_button_size">20dp</dimen>
   <dimen name="done_step_link_button_marginHorizontal">4dp</dimen>
+  <dimen name="sb_bottom_sheet_toolbar_height">20dp</dimen>
 </resources>
diff --git a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideBottomSheetView.java b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideBottomSheetView.java
index 6a36c20..f2a8762d 100644
--- a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideBottomSheetView.java
+++ b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideBottomSheetView.java
@@ -13,9 +13,11 @@
 /** Bottom sheet view for displaying privacy guide control explanations */
 public class PrivacyGuideBottomSheetView implements BottomSheetContent {
     private final View mContentView;
+    private final View mToolbarView;
 
-    PrivacyGuideBottomSheetView(View contentView) {
+    PrivacyGuideBottomSheetView(View contentView, View toolbarView) {
         mContentView = contentView;
+        mToolbarView = toolbarView;
     }
 
     @Override
@@ -26,12 +28,12 @@
     @Nullable
     @Override
     public View getToolbarView() {
-        return null;
+        return mToolbarView;
     }
 
     @Override
     public int getVerticalScrollOffset() {
-        return 0;
+        return mContentView.getScrollY();
     }
 
     @Override
diff --git a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SafeBrowsingFragment.java b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SafeBrowsingFragment.java
index c17b394a..a36d854 100644
--- a/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SafeBrowsingFragment.java
+++ b/chrome/browser/privacy_guide/android/java/src/org/chromium/chrome/browser/privacy_guide/SafeBrowsingFragment.java
@@ -69,10 +69,12 @@
         LayoutInflater inflater = LayoutInflater.from(getView().getContext());
         if (clickedButtonId == mEnhancedProtection.getId()) {
             displayBottomSheet(
-                    inflater.inflate(R.layout.privacy_guide_sb_enhanced_explanation, null));
+                    inflater.inflate(R.layout.privacy_guide_sb_enhanced_explanation, null),
+                    inflater.inflate(R.layout.privacy_guide_sb_bottom_sheet_toolbar, null));
         } else if (clickedButtonId == mStandardProtection.getId()) {
             displayBottomSheet(
-                    inflater.inflate(R.layout.privacy_guide_sb_standard_explanation, null));
+                    inflater.inflate(R.layout.privacy_guide_sb_standard_explanation, null),
+                    inflater.inflate(R.layout.privacy_guide_sb_bottom_sheet_toolbar, null));
         } else {
             assert false : "Unknown Aux clickedButtonId " + clickedButtonId;
         }
@@ -93,8 +95,9 @@
         }
     }
 
-    private void displayBottomSheet(View sheetContent) {
-        PrivacyGuideBottomSheetView bottomSheet = new PrivacyGuideBottomSheetView(sheetContent);
+    private void displayBottomSheet(View sheetContent, View sheetToolbar) {
+        PrivacyGuideBottomSheetView bottomSheet =
+                new PrivacyGuideBottomSheetView(sheetContent, sheetToolbar);
         mBottomSheetController.requestShowContent(bottomSheet, /* animate= */ true);
     }
 
diff --git a/chrome/browser/privacy_guide/android/javatests/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideFragmentTest.java b/chrome/browser/privacy_guide/android/javatests/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideFragmentTest.java
index fcd53ffa..4dd4352 100644
--- a/chrome/browser/privacy_guide/android/javatests/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideFragmentTest.java
+++ b/chrome/browser/privacy_guide/android/javatests/src/org/chromium/chrome/browser/privacy_guide/PrivacyGuideFragmentTest.java
@@ -10,11 +10,15 @@
 import static androidx.test.espresso.intent.Intents.intended;
 import static androidx.test.espresso.intent.Intents.intending;
 import static androidx.test.espresso.intent.matcher.IntentMatchers.anyIntent;
+import static androidx.test.espresso.matcher.ViewMatchers.hasSibling;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withChild;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static androidx.test.espresso.matcher.ViewMatchers.withParent;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
 import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.allOf;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -29,8 +33,7 @@
 
 import androidx.test.espresso.intent.Intents;
 import androidx.test.espresso.intent.matcher.IntentMatchers;
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.LargeTest;
 
 import org.junit.After;
 import org.junit.Before;
@@ -54,11 +57,10 @@
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.browser.signin.services.UnifiedConsentServiceBridge;
 import org.chromium.chrome.browser.sync.SyncService;
+import org.chromium.chrome.test.ChromeBrowserTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.content_settings.CookieControlsMode;
 import org.chromium.components.content_settings.PrefNames;
 import org.chromium.components.embedder_support.util.UrlConstants;
@@ -84,17 +86,13 @@
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Rule
-    public final ChromeTabbedActivityTestRule mActivityTestRule =
-            new ChromeTabbedActivityTestRule();
+    public ChromeBrowserTestRule mChromeBrowserTestRule = new ChromeBrowserTestRule();
 
     @Rule
     public SettingsActivityTestRule<PrivacyGuideFragment> mSettingsActivityTestRule =
             new SettingsActivityTestRule<>(PrivacyGuideFragment.class);
 
     @Rule
-    public SigninTestRule mSigninTestRule = new SigninTestRule();
-
-    @Rule
     public ChromeRenderTestRule mRenderTestRule =
             ChromeRenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(ChromeRenderTestRule.Component.UI_SETTINGS_PRIVACY)
@@ -107,8 +105,7 @@
 
     @Before
     public void setUp() {
-        mActivityTestRule.startMainActivityOnBlankPage();
-        mSigninTestRule.addTestAccountThenSigninAndEnableSync();
+        mChromeBrowserTestRule.addTestAccountThenSigninAndEnableSync();
         mActionTester = new UserActionTester();
     }
 
@@ -248,8 +245,14 @@
                 .getRootView();
     }
 
+    private void clickOnArrowNextToRadioButtonWithText(int textId) {
+        onView(allOf(withId(R.id.expand_arrow),
+                       withParent(hasSibling(withChild(withText(textId))))))
+                .perform(click());
+    }
+
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderWelcomeCard() throws IOException {
         launchPrivacyGuide();
@@ -257,7 +260,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderMSBBCard() throws IOException {
         launchPrivacyGuide();
@@ -266,7 +269,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderHistorySyncCard() throws IOException {
         launchPrivacyGuide();
@@ -275,7 +278,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderSBCard() throws IOException {
         launchPrivacyGuide();
@@ -284,7 +287,27 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
+    @Feature({"RenderTest"})
+    public void testRenderSBEnhancedBottomSheet() throws IOException {
+        launchPrivacyGuide();
+        goToSafeBrowsingCard();
+        clickOnArrowNextToRadioButtonWithText(R.string.privacy_guide_safe_browsing_enhanced_title);
+        mRenderTestRule.render(getRootView(), "privacy_guide_sb_enhanced_sheet");
+    }
+
+    @Test
+    @LargeTest
+    @Feature({"RenderTest"})
+    public void testRenderSBStandardBottomSheet() throws IOException {
+        launchPrivacyGuide();
+        goToSafeBrowsingCard();
+        clickOnArrowNextToRadioButtonWithText(R.string.privacy_guide_safe_browsing_standard_title);
+        mRenderTestRule.render(getRootView(), "privacy_guide_sb_standard_sheet");
+    }
+
+    @Test
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderCookiesCard() throws IOException {
         launchPrivacyGuide();
@@ -293,7 +316,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"RenderTest"})
     public void testRenderCompletionCard() throws IOException {
         launchPrivacyGuide();
@@ -302,7 +325,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testForwardNavigation() {
         launchPrivacyGuide();
@@ -321,7 +344,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testWelcomeCard_nextClickWelcomeUserAction() {
         launchPrivacyGuide();
@@ -332,7 +355,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testWelcomeCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -346,7 +369,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_nextClickCompletionUserAction() {
         launchPrivacyGuide();
@@ -362,7 +385,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -378,7 +401,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     @Features.EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_3)
     @Features.DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4)
@@ -392,7 +415,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     @Features.EnableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_4)
     @Features.DisableFeatures(ChromeFeatureList.PRIVACY_SANDBOX_SETTINGS_3)
@@ -405,7 +428,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_AdPrivacyClickUserAction() {
         launchPrivacyGuide();
@@ -416,7 +439,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_AdPrivacyClickHistogram() {
         launchPrivacyGuide();
@@ -431,7 +454,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_WaaLinkNavigation() {
         launchPrivacyGuide();
@@ -445,7 +468,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_WaaClickUserAction() {
         launchPrivacyGuide();
@@ -458,7 +481,7 @@
     }
 
     @Test
-    @MediumTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCompletionCard_WaaClickHistogram() {
         launchPrivacyGuide();
@@ -473,7 +496,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_nextClickMSBBUserAction() {
         launchPrivacyGuide();
@@ -483,7 +506,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -498,7 +521,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_offToOffSettingsStatesHistogram() {
         setMSBBState(false);
@@ -514,7 +537,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_offToOnSettingsStatesHistogram() {
         setMSBBState(false);
@@ -531,7 +554,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_onToOffSettingsStatesHistogram() {
         setMSBBState(true);
@@ -548,7 +571,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_onToOnSettingsStatesHistogram() {
         setMSBBState(true);
@@ -564,7 +587,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_nextButtonInitialMSBBStateIsSet() {
         launchPrivacyGuide();
@@ -579,7 +602,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testMSBBCard_backButtonInitialMSBBStateIsSet() {
         launchPrivacyGuide();
@@ -596,7 +619,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_nextClickHistorySyncUserAction() {
         launchPrivacyGuide();
@@ -608,7 +631,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -623,7 +646,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_offToOffSettingsStatesHistogram() {
         setHistorySyncState(false);
@@ -639,7 +662,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_offToOnSettingsStatesHistogram() {
         setHistorySyncState(false);
@@ -656,7 +679,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_onToOffSettingsStatesHistogram() {
         setHistorySyncState(true);
@@ -673,7 +696,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_onToOnSettingsStatesHistogram() {
         setHistorySyncState(true);
@@ -689,7 +712,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_nextButtonInitialSyncStateIsSet() {
         launchPrivacyGuide();
@@ -704,7 +727,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_backButtonInitialSyncStateIsSet() {
         launchPrivacyGuide();
@@ -721,7 +744,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_nextClickSafeBrowsingUserAction() {
         launchPrivacyGuide();
@@ -732,7 +755,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -747,7 +770,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_standardToStandardSettingsStatesHistogram() {
         setSafeBrowsingState(SafeBrowsingState.STANDARD_PROTECTION);
@@ -763,7 +786,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_standardToEnhancedSettingsStatesHistogram() {
         setSafeBrowsingState(SafeBrowsingState.STANDARD_PROTECTION);
@@ -780,7 +803,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_enhancedToEnhancedSettingsStatesHistogram() {
         setSafeBrowsingState(SafeBrowsingState.ENHANCED_PROTECTION);
@@ -796,7 +819,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_enhancedToStandardSettingsStatesHistogram() {
         setSafeBrowsingState(SafeBrowsingState.ENHANCED_PROTECTION);
@@ -813,7 +836,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_nextButtonInitialSafeBrowsingStateIsSet() {
         launchPrivacyGuide();
@@ -828,7 +851,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_backButtonInitialSafeBrowsingStateIsSet() {
         launchPrivacyGuide();
@@ -845,7 +868,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_nextClickCookiesUserAction() {
         launchPrivacyGuide();
@@ -855,7 +878,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_nextNavigationHistogram() {
         launchPrivacyGuide();
@@ -870,7 +893,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_block3PIncognitoTo3PIncognitoSettingsStatesHistogram() {
         setCookieControlsMode(CookieControlsMode.INCOGNITO_ONLY);
@@ -886,7 +909,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_block3PIncognitoTo3PSettingsStatesHistogram() {
         setCookieControlsMode(CookieControlsMode.INCOGNITO_ONLY);
@@ -903,7 +926,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_block3PTo3PIncognitoSettingsStatesHistogram() {
         setCookieControlsMode(CookieControlsMode.BLOCK_THIRD_PARTY);
@@ -920,7 +943,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_block3PTo3PSettingsStatesHistogram() {
         setCookieControlsMode(CookieControlsMode.BLOCK_THIRD_PARTY);
@@ -936,7 +959,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_nextButtonInitialCookiesStateIsSet() {
         launchPrivacyGuide();
@@ -951,7 +974,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testHistorySyncCard_backClickHistorySyncUserAction() {
         launchPrivacyGuide();
@@ -966,7 +989,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testSafeBrowsingCard_backClickSafeBrowsingUserAction() {
         launchPrivacyGuide();
@@ -981,7 +1004,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     @Feature({"PrivacyGuide"})
     public void testCookiesCard_backClickCookiesUserAction() {
         launchPrivacyGuide();
diff --git a/chrome/browser/profiles/profile.cc b/chrome/browser/profiles/profile.cc
index a2a5795..6cffe94 100644
--- a/chrome/browser/profiles/profile.cc
+++ b/chrome/browser/profiles/profile.cc
@@ -8,7 +8,6 @@
 
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
-#include "base/guid.h"
 #include "base/memory/raw_ptr.h"
 #include "base/observer_list.h"
 #include "base/path_service.h"
@@ -17,6 +16,7 @@
 #include "base/time/time.h"
 #include "base/trace_event/typed_macros.h"
 #include "base/tracing/protos/chrome_track_event.pbzero.h"
+#include "base/uuid.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
@@ -138,7 +138,7 @@
     const std::string& profile_id_prefix) {
   return OTRProfileID(base::StringPrintf(
       "%s-%s", profile_id_prefix.c_str(),
-      base::GUID::GenerateRandomV4().AsLowercaseString().c_str()));
+      base::Uuid::GenerateRandomV4().AsLowercaseString().c_str()));
 }
 
 // static
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index 46865b3..bf7553ea 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -210,6 +210,7 @@
   std::set<std::string> guest_otr_active_services {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
     "CleanupManagerLacros",
+    "DownloadBubbleUpdateService",
     "DownloadCoreService",
 #else
     "LiveCaptionController",
diff --git a/chrome/browser/push_messaging/budget_database.cc b/chrome/browser/push_messaging/budget_database.cc
index 06a3bd9..9222ad12 100644
--- a/chrome/browser/push_messaging/budget_database.cc
+++ b/chrome/browser/push_messaging/budget_database.cc
@@ -6,7 +6,6 @@
 
 #include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
 #include "base/time/clock.h"
@@ -193,9 +192,6 @@
     return;
   }
 
-  // Get the current SES score, to generate UMA.
-  double score = GetSiteEngagementScoreForOrigin(origin);
-
   // Walk the list of budget chunks to see if the origin has enough budget.
   double total = 0;
   BudgetInfo& info = budget_map_[origin];
@@ -203,11 +199,8 @@
     total += chunk.amount;
 
   if (total < amount) {
-    UMA_HISTOGRAM_COUNTS_100("PushMessaging.SESForNoBudgetOrigin", score);
     std::move(callback).Run(false /* success */);
     return;
-  } else if (total < amount * 2) {
-    UMA_HISTOGRAM_COUNTS_100("PushMessaging.SESForLowBudgetOrigin", score);
   }
 
   // Walk the chunks and remove enough budget to cover the needed amount.
@@ -355,11 +348,6 @@
   base::Time expiration = clock_->Now() + base::Days(kBudgetDurationInDays);
   budget_map_[origin].chunks.emplace_back(elapsed.InHours() * hourly_budget,
                                           expiration);
-
-  // Any time we award engagement budget, which is done at most once an hour
-  // whenever any budget action is taken, record the budget.
-  double budget = GetBudget(origin);
-  UMA_HISTOGRAM_COUNTS_100("PushMessaging.BackgroundBudget", budget);
 }
 
 // Cleans up budget in the cache. Relies on the caller eventually writing the
diff --git a/chrome/browser/push_messaging/budget_database_unittest.cc b/chrome/browser/push_messaging/budget_database_unittest.cc
index d9892f8..296a570 100644
--- a/chrome/browser/push_messaging/budget_database_unittest.cc
+++ b/chrome/browser/push_messaging/budget_database_unittest.cc
@@ -245,89 +245,6 @@
   ASSERT_EQ(budget, prediction_[0].budget_at);
 }
 
-TEST_F(BudgetDatabaseTest, CheckBackgroundBudgetHistogram) {
-  base::SimpleTestClock* clock = SetClockForTesting();
-
-  // Set the default site engagement.
-  SetSiteEngagementScore(kEngagement);
-
-  // Initialize the budget with some interesting chunks: 30 budget (full
-  // engagement), 15 budget (half of the engagement), 0 budget (less than an
-  // hour), and then after the first two expire, another 30 budget.
-  GetBudgetDetails();
-  clock->Advance(base::Days(kDefaultExpirationInDays / 2));
-  GetBudgetDetails();
-  clock->Advance(base::Minutes(59));
-  GetBudgetDetails();
-  clock->Advance(base::Days(kDefaultExpirationInDays + 1));
-  GetBudgetDetails();
-
-  // The BackgroundBudget UMA is recorded when budget is added to the origin.
-  // This can happen a maximum of once per hour so there should be two entries.
-  std::vector<base::Bucket> buckets =
-      GetHistogramTester()->GetAllSamples("PushMessaging.BackgroundBudget");
-  ASSERT_EQ(2U, buckets.size());
-  // First bucket is for full award, which should have 2 entries.
-  double full_award = kMaxDailyBudget * kEngagement /
-                      site_engagement::SiteEngagementScore::kMaxPoints *
-                      kDefaultExpirationInDays;
-  EXPECT_EQ(floor(full_award), buckets[0].min);
-  EXPECT_EQ(2, buckets[0].count);
-  // Second bucket is for 1.5 * award, which should have 1 entry.
-  EXPECT_EQ(floor(full_award * 1.5), buckets[1].min);
-  EXPECT_EQ(1, buckets[1].count);
-}
-
-TEST_F(BudgetDatabaseTest, CheckEngagementHistograms) {
-  base::SimpleTestClock* clock = SetClockForTesting();
-
-  // Manipulate the engagement so that the budget is twice the cost of an
-  // action.
-  double cost = 2;
-  double engagement = 2 * cost *
-                      site_engagement::SiteEngagementScore::kMaxPoints /
-                      kDefaultExpirationInDays / kMaxDailyBudget;
-  SetSiteEngagementScore(engagement);
-
-  // Get the budget, which will award a chunk of budget equal to engagement.
-  GetBudgetDetails();
-
-  // Now spend the budget to trigger the UMA recording the SES score. The first
-  // call shouldn't write any UMA. The second should write a lowSES entry, and
-  // the third should write a noSES entry.
-  ASSERT_TRUE(SpendBudget(cost));
-  ASSERT_TRUE(SpendBudget(cost));
-  ASSERT_FALSE(SpendBudget(cost));
-
-  // Advance the clock by 12 days (to guarantee a full new engagement grant)
-  // then change the SES score to get a different UMA entry, then spend the
-  // budget again.
-  clock->Advance(base::Days(12));
-  GetBudgetDetails();
-  SetSiteEngagementScore(engagement * 2);
-  ASSERT_TRUE(SpendBudget(cost));
-  ASSERT_TRUE(SpendBudget(cost));
-  ASSERT_FALSE(SpendBudget(cost));
-
-  // Now check the UMA. Both UMA should have 2 buckets with 1 entry each.
-  std::vector<base::Bucket> no_budget_buckets =
-      GetHistogramTester()->GetAllSamples("PushMessaging.SESForNoBudgetOrigin");
-  ASSERT_EQ(2U, no_budget_buckets.size());
-  EXPECT_EQ(floor(engagement), no_budget_buckets[0].min);
-  EXPECT_EQ(1, no_budget_buckets[0].count);
-  EXPECT_EQ(floor(engagement * 2), no_budget_buckets[1].min);
-  EXPECT_EQ(1, no_budget_buckets[1].count);
-
-  std::vector<base::Bucket> low_budget_buckets =
-      GetHistogramTester()->GetAllSamples(
-          "PushMessaging.SESForLowBudgetOrigin");
-  ASSERT_EQ(2U, low_budget_buckets.size());
-  EXPECT_EQ(floor(engagement), low_budget_buckets[0].min);
-  EXPECT_EQ(1, low_budget_buckets[0].count);
-  EXPECT_EQ(floor(engagement * 2), low_budget_buckets[1].min);
-  EXPECT_EQ(1, low_budget_buckets[1].count);
-}
-
 TEST_F(BudgetDatabaseTest, DefaultSiteEngagementInIncognitoProfile) {
   TestingProfile second_profile;
   Profile* second_profile_incognito =
diff --git a/chrome/browser/resources/password_manager/password_manager_proxy.ts b/chrome/browser/resources/password_manager/password_manager_proxy.ts
index 3816b29..ba31a1a 100644
--- a/chrome/browser/resources/password_manager/password_manager_proxy.ts
+++ b/chrome/browser/resources/password_manager/password_manager_proxy.ts
@@ -249,6 +249,14 @@
       Promise<chrome.passwordsPrivate.ImportResults>;
 
   /**
+   * Resets the PasswordImporter if it is in the CONFLICTS/FINISHED state and
+   * the user closes the dialog. Only when the PasswordImporter is in FINISHED
+   * state, |deleteFile| option is taken into account.
+   * @param deleteFile Whether to trigger deletion of the last imported file.
+   */
+  resetImporter(deleteFile: boolean): Promise<void>;
+
+  /**
    * Queries the status of any ongoing export.
    */
   requestExportProgressStatus():
@@ -485,6 +493,10 @@
     return chrome.passwordsPrivate.importPasswords(toStore);
   }
 
+  resetImporter(deleteFile: boolean) {
+    return chrome.passwordsPrivate.resetImporter(deleteFile);
+  }
+
   requestExportProgressStatus() {
     return chrome.passwordsPrivate.requestExportProgressStatus();
   }
diff --git a/chrome/browser/resources/password_manager/passwords_importer.html b/chrome/browser/resources/password_manager/passwords_importer.html
index f1a16794..3b0ffd9 100644
--- a/chrome/browser/resources/password_manager/passwords_importer.html
+++ b/chrome/browser/resources/password_manager/passwords_importer.html
@@ -83,6 +83,11 @@
     padding: 8px;
   }
 
+  #deleteFileOption {
+    margin-top: 16px;
+    --cr-checkbox-label-padding-start: 15px;
+  }
+
   @media (prefers-color-scheme: dark) {
     #tipBox {
       background: var(--google-grey-900);
@@ -190,6 +195,10 @@
         <div id="successTip" inner-h-t-m-l="[[getSuccessTipHtml_(results_)]]">
         </div>
       </div>
+      <cr-checkbox id="deleteFileOption"
+        hidden="[[shouldHideDeleteFileOption_(results_)]]"
+        inner-h-t-m-l="[[getCheckboxLabelHtml_(results_)]]">
+      </cr-checkbox>
       <div hidden="[[shouldHideFailuresSummary_(results_)]]">
         <div id="failuresTitleRow" class="flex-centered">
           <iron-icon class="error-icon" icon="cr:warning"></iron-icon>
diff --git a/chrome/browser/resources/password_manager/passwords_importer.ts b/chrome/browser/resources/password_manager/passwords_importer.ts
index 50f85e6..6bb9ca18 100644
--- a/chrome/browser/resources/password_manager/passwords_importer.ts
+++ b/chrome/browser/resources/password_manager/passwords_importer.ts
@@ -9,6 +9,7 @@
 import 'chrome://resources/polymer/v3_0/paper-spinner/paper-spinner-lite.js';
 import './site_favicon.js';
 
+import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.js';
 import {CrLinkRowElement} from 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {assert, assertNotReached} from 'chrome://resources/js/assert_ts.js';
@@ -47,6 +48,13 @@
 
   static get properties() {
     return {
+      enablePasswordsImportM2_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('enablePasswordsImportM2');
+        },
+      },
+
       inProgress_: {
         type: Boolean,
         value: false,
@@ -103,6 +111,7 @@
   isAccountStoreUser: boolean;
   accountEmail: string;
 
+  private enablePasswordsImportM2_: boolean;
   private inProgress_: boolean;
   private dialogState_: DialogState = DialogState.NO_DIALOG;
   // Refers both to syncing users with sync enabled for passwords and account
@@ -171,11 +180,28 @@
     // dialog is closed.
   }
 
-  private onCloseClick_() {
+  private async resetImporter() {
+    let deleteFile = false;
+    if (this.isState_(DialogState.SUCCESS) &&
+        !this.shouldHideDeleteFileOption_()) {
+      // Trigger the file deletion if checkbox is ticked in SUCCESS (with no
+      // errors) state.
+      const deleteFileOption =
+          this.shadowRoot!.querySelector<CrCheckboxElement>(
+              '#deleteFileOption');
+      assert(deleteFileOption);
+      deleteFile = deleteFileOption.checked;
+    }
+    await this.passwordManager_.resetImporter(deleteFile);
+  }
+
+  private async onCloseClick_() {
+    await this.resetImporter();
     this.closeDialog_();
   }
 
-  private onViewPasswordsClick_() {
+  private async onViewPasswordsClick_() {
+    await this.resetImporter();
     this.closeDialog_();
     Router.getInstance().navigateTo(Page.PASSWORDS);
   }
@@ -317,13 +343,33 @@
         {attrs: ['class'], substitutions: [this.results_.fileName]});
   }
 
+  private getCheckboxLabelHtml_(): TrustedHTML {
+    assert(this.results_);
+    return this.i18nAdvanced(
+        'importPasswordsDeleteFileOption',
+        {attrs: ['class'], substitutions: [this.results_.fileName]});
+  }
+
   private shouldHideLinkRowIcon_(): boolean {
     return this.inProgress_ || this.showSelectFileButton_;
   }
 
   private shouldHideTipBox_(): boolean {
     // Tip box is only shown in "success" state if all passwords were imported.
-    // TODO(crbug/1432962): Also hide when import M2 is enabled.
+    // Only shown in Passwords Import M1.
+    if (this.enablePasswordsImportM2_) {
+      return true;
+    }
+    assert(this.results_);
+    return !!this.results_.displayedEntries.length;
+  }
+
+  private shouldHideDeleteFileOption_(): boolean {
+    // "Delete file" checkbox is only shown in "success" state if all passwords
+    // were imported.
+    if (!this.enablePasswordsImportM2_) {
+      return true;
+    }
     assert(this.results_);
     return !!this.results_.displayedEntries.length;
   }
diff --git a/chrome/browser/resources/password_manager/settings_section.ts b/chrome/browser/resources/password_manager/settings_section.ts
index 5796650..e2af72e1 100644
--- a/chrome/browser/resources/password_manager/settings_section.ts
+++ b/chrome/browser/resources/password_manager/settings_section.ts
@@ -101,6 +101,14 @@
   private setCredentialsChangedListener_: CredentialsChangedListener|null =
       null;
 
+  override ready() {
+    super.ready();
+
+    chrome.metricsPrivate.recordBoolean(
+        'PasswordManager.OpenedAsShortcut',
+        window.matchMedia('(display-mode: standalone)').matches);
+  }
+
   override connectedCallback() {
     super.connectedCallback();
     this.setBlockedSitesListListener_ = blockedSites => {
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.html b/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.html
index b456711..69329106 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.html
@@ -1,5 +1,82 @@
-<style include="settings-shared">
+<style include="settings-shared network-shared">
+  /* Align the headline text on the page title. */
+  #headlineLink {
+    margin-inline-start: 36px;
+  }
 </style>
-<div>
-  <!-- TODO(b/266151248) add Passpoint details content. -->
+<div class="settings-box first two-line">
+  <localized-link id="headlineLink" class="secondary"
+      localized-string="$i18n{passpointHeadlineText}"
+      link-url="$i18nRaw{wifiPasspointLearnMoreUrl}">
+  </localized-link>
+  <cr-button id="removeButton" on-click="onForgetTap_">
+    $i18n{passpointRemoveButton}
+  </cr-button>
 </div>
+<template is="dom-if" if="[[hasExpirationDate_(subscription_)]]">
+  <div class="settings-box first two-line single-column"
+      aria-labelledby="passpointExpirationLabel passpointExpirationDate">
+    <div id="passpointExpirationLabel" aria-hidden="true">
+      $i18n{passpointSubscriptionExpirationLabel}
+    </div>
+    <div id="passpointExpirationDate" class="secondary" aria-hidden="true">
+      [[getExpirationDate_(subscription_)]]
+    </div>
+  </div>
+</template>
+<div class="settings-box two-line single-column"
+    aria-labelledby="passpointSourceLabel passpointSourceText">
+  <div id="passpointSourceLabel" aria-hidden="true">
+    $i18n{passpointSourceLabel}
+  </div>
+  <div id="passpointSourceText" class="secondary" aria-hidden="true">
+    [[providerName_]]
+  </div>
+</div>
+<div class="settings-box two-line single-column"
+    aria-labelledby="passpointTrustedCALabel passpointCertificateName">
+  <div id="passpointTrustedCALabel" aria-hidden="true">
+    $i18n{passpointTrustedCALabel}
+  </div>
+  <div id="passpointCertificateName" class="secondary" aria-hidden="true">
+    [[certificateAuthorityName_]]
+  </div>
+</div>
+<cr-expand-button aria-label="$i18n{passpointDomainsA11yLabel}"
+    class="settings-box" expanded="{{domainsExpanded_}}">
+  $i18n{passpointDomainsLabel}
+</cr-expand-button>
+<div id="passpointDomainsList" class="list-frame vertical-list">
+  <iron-collapse opened="[[domainsExpanded_]]">
+    <template is="dom-repeat"
+        items="[[getPasspointDomainsList_(subscription_)]]">
+      <div class="list-item secondary">
+        [[item]]
+      </div>
+    </template>
+  </iron-collapse>
+</div>
+<!-- Removal dialog triggered by the "forget" button -->
+<template is="dom-if" if="[[showForgetDialog_]]" restamp>
+  <cr-dialog id="removalDialog" close-text="$i18n{close}" show-on-attach>
+    <div slot="title">
+      $i18nPolymer{passpointRemovalTitle}
+    </div>
+    <div slot="body">
+      <localized-link
+          localized-string="[[getRemovalDialogDescription_(subscription_)]]"
+          link-url="$i18nRaw{wifiPasspointLearnMoreUrl}">
+      </localized-link>
+    </div>
+    <div slot="button-container">
+      <cr-button id="removalCancelButton" class="cancel-button"
+          on-click="onRemovalDialogCancel_">
+        $i18n{cancel}
+      </cr-button>
+      <cr-button id="removalConfirmButton" class="action-button"
+          on-click="onRemovalDialogConfirm_">
+        $i18n{confirm}
+      </cr-button>
+    </div>
+  </cr-dialog>
+</template>
diff --git a/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.ts b/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.ts
index f74f0b86..6596ee06 100644
--- a/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.ts
+++ b/chrome/browser/resources/settings/chromeos/internet_page/passpoint_subpage.ts
@@ -9,15 +9,25 @@
 
 import '../../settings_shared.css.js';
 
+import {MojoConnectivityProvider} from 'chrome://resources/ash/common/connectivity/mojo_connectivity_provider.js';
+import {PasspointServiceInterface, PasspointSubscription} from 'chrome://resources/ash/common/connectivity/passpoint.mojom-webui.js';
+import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
+import {App, AppType, PageHandlerInterface} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
+import {BrowserProxy as AppManagementComponentBrowserProxy} from 'chrome://resources/cr_components/app_management/browser_proxy.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {CrosNetworkConfigRemote, NetworkCertificate} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {castExists} from '../assert_extras.js';
+import {routes} from '../os_settings_routes.js';
+import {RouteObserverMixin} from '../route_observer_mixin.js';
+import {Route, Router} from '../router.js';
+
+import {PasspointListenerMixin} from './passpoint_listener_mixin.js';
 import {getTemplate} from './passpoint_subpage.html.js';
 
-const SettingsPasspointSubpageElementBase = I18nMixin(PolymerElement);
-
-class SettingsPasspointSubpageElement extends
-    SettingsPasspointSubpageElementBase {
+export class SettingsPasspointSubpageElement extends PasspointListenerMixin
+(RouteObserverMixin(I18nMixin(PolymerElement))) {
   static get is() {
     return 'settings-passpoint-subpage' as const;
   }
@@ -27,11 +37,181 @@
   }
 
   static get properties() {
-    return {};
+    return {
+      /** The identifier of the subscription for which details are shown. */
+      id_: String,
+
+      /** Passpoint subscription currently displayed. */
+      subscription_: Object,
+
+      /** ARC application that provided the subscription. */
+      app_: Object,
+
+      /** List of Certificate Authorities available. */
+      certs_: Array,
+
+      /** Certificate authority common name. */
+      certificateAuthorityName_: {
+        type: String,
+        computed: 'getCertificateAuthorityName_(certs_)',
+      },
+
+      /** Name of the provider of the subscription. */
+      providerName_: {
+        type: String,
+        computed: 'getProviderName_(subscription_, app_)',
+      },
+
+      /** Tell if the forget dialog should be displayed. */
+      showForgetDialog_: Boolean,
+
+      domainsExpanded_: Boolean,
+    };
   }
 
+  private app_: App|null;
+  private appHandler_: PageHandlerInterface;
+  private certs_: NetworkCertificate[];
+  private certificateAuthorityName_: string;
+  private domainsExpanded_: boolean;
+  private id_: string;
+  private networkConfig_: CrosNetworkConfigRemote;
+  private passpointService_: PasspointServiceInterface;
+  private providerName_: string;
+  private showForgetDialog_: boolean;
+  private subscription_: PasspointSubscription|null;
+
   constructor() {
     super();
+    this.networkConfig_ =
+        MojoInterfaceProviderImpl.getInstance().getMojoServiceRemote();
+    this.passpointService_ =
+        MojoConnectivityProvider.getInstance().getPasspointService();
+    this.appHandler_ = AppManagementComponentBrowserProxy.getInstance().handler;
+  }
+
+  close(): void {
+    // If the page is already closed, return early to avoid navigating backward
+    // erroneously.
+    if (!this.id_) {
+      return;
+    }
+
+    this.id_ = '';
+    Router.getInstance().navigateToPreviousRoute();
+  }
+
+
+  /**
+   * RouteObserverMixin override
+   */
+  override currentRouteChanged(route: Route): void {
+    if (route !== routes.PASSPOINT_DETAIL) {
+      return;
+    }
+
+    const queryParams = Router.getInstance().getQueryParameters();
+    const id = queryParams.get('id') || '';
+    if (!id) {
+      console.warn('No Passpoint subscription ID specified for page:' + route);
+      this.close();
+      return;
+    }
+    this.id_ = id;
+    this.refresh_();
+  }
+
+  private async refresh_(): Promise<void> {
+    const response =
+        await this.passpointService_.getPasspointSubscription(this.id_);
+    if (!response.result) {
+      console.warn('No subscription found for id ' + this.id_);
+      this.close();
+      return;
+    }
+    this.subscription_ = response.result;
+    this.refreshCertificates_();
+    this.refreshApp_(this.subscription_);
+  }
+
+  private async refreshCertificates_() {
+    const certs = await this.networkConfig_.getNetworkCertificates();
+    this.certs_ = certs.serverCas;
+  }
+
+  private async refreshApp_(subscription: PasspointSubscription) {
+    const response = await this.appHandler_.getApps();
+    for (const app of response.apps) {
+      if (app.type === AppType.kArc &&
+          app.publisherId === subscription.provisioningSource) {
+        this.app_ = app;
+        return;
+      }
+    }
+  }
+
+  private getCertificateAuthorityName_() {
+    for (const cert of this.certs_) {
+      if (cert.pemOrId === this.subscription_!.trustedCa) {
+        return cert.issuedTo;
+      }
+    }
+    return this.i18n('passpointSystemCALabel');
+  }
+
+  private hasExpirationDate_(): boolean {
+    return this.subscription_!.expirationEpochMs > 0n;
+  }
+
+  private getExpirationDate_(subscription: PasspointSubscription): string {
+    const date = new Date(Number(subscription.expirationEpochMs));
+    return date.toLocaleDateString();
+  }
+
+  private getProviderName_(): string {
+    if (this.app_ && this.app_!.title !== undefined) {
+      return this.app_!.title;
+    }
+    return this.subscription_!.provisioningSource;
+  }
+
+  private getPasspointDomainsList_(): string[] {
+    return this.subscription_!.domains;
+  }
+
+  private getRemovalDialogDescription_() {
+    return this.i18n(
+        'passpointRemovalDescription', this.subscription_!.friendlyName);
+  }
+
+  private getRemovalDialog_(): HTMLDialogElement {
+    return castExists(
+        this.shadowRoot!.querySelector<HTMLDialogElement>('#removalDialog'));
+  }
+
+  private onForgetTap_() {
+    this.showForgetDialog_ = true;
+  }
+
+  private async onRemovalDialogConfirm_() {
+    this.showForgetDialog_ = false;
+    const response =
+        await this.passpointService_.deletePasspointSubscription(this.id_);
+    if (response.success) {
+      this.close();
+      return;
+    }
+  }
+
+  private onRemovalDialogCancel_() {
+    this.showForgetDialog_ = false;
+  }
+
+  override onPasspointSubscriptionRemoved(subscription: PasspointSubscription) {
+    if (this.id_ === subscription.id) {
+      // The subscription was removed, leave the page.
+      this.close();
+    }
   }
 }
 
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.ts b/chrome/browser/resources/settings/chromeos/lazy_load.ts
index 1e23e8e..965091f1 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.ts
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.ts
@@ -112,6 +112,7 @@
 export {TimezoneSubpageElement} from './date_time_page/timezone_subpage.js';
 export {CROSTINI_TYPE, GuestOsBrowserProxy, GuestOsBrowserProxyImpl, GuestOsSharedUsbDevice, PLUGIN_VM_TYPE} from './guest_os/guest_os_browser_proxy.js';
 export {SettingsGuestOsSharedUsbDevicesElement} from './guest_os/guest_os_shared_usb_devices.js';
+export {SettingsPasspointSubpageElement} from './internet_page/passpoint_subpage.js';
 export {TetherConnectionDialogElement} from './internet_page/tether_connection_dialog.js';
 export {KeyboardShortcutBanner} from './keyboard_shortcut_banner/keyboard_shortcut_banner.js';
 export {SettingsMultideviceCombinedSetupItemElement} from './multidevice_page/multidevice_combined_setup_item.js';
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_config.cc b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
index a869551..885f535 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_config.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_config.cc
@@ -13,14 +13,12 @@
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "components/segmentation_platform/embedder/default_model/cross_device_user_segment.h"
 #include "components/segmentation_platform/embedder/default_model/device_switcher_model.h"
-#include "components/segmentation_platform/embedder/default_model/device_tier_segment.h"
 #include "components/segmentation_platform/embedder/default_model/feed_user_segment.h"
 #include "components/segmentation_platform/embedder/default_model/frequent_feature_user_model.h"
 #include "components/segmentation_platform/embedder/default_model/low_user_engagement_model.h"
 #include "components/segmentation_platform/embedder/default_model/resume_heavy_user_model.h"
 #include "components/segmentation_platform/embedder/default_model/search_user_model.h"
 #include "components/segmentation_platform/embedder/default_model/shopping_user_model.h"
-#include "components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.h"
 #include "components/segmentation_platform/internal/config_parser.h"
 #include "components/segmentation_platform/public/config.h"
 #include "components/segmentation_platform/public/constants.h"
@@ -38,9 +36,11 @@
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/shopping_service.h"
 #include "components/segmentation_platform/embedder/default_model/contextual_page_actions_model.h"
+#include "components/segmentation_platform/embedder/default_model/device_tier_segment.h"
 #include "components/segmentation_platform/embedder/default_model/intentional_user_model.h"
 #include "components/segmentation_platform/embedder/default_model/power_user_segment.h"
 #include "components/segmentation_platform/embedder/default_model/query_tiles_model.h"
+#include "components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.h"
 #endif
 
 namespace segmentation_platform {
diff --git a/chrome/browser/signin/chrome_device_id_helper.cc b/chrome/browser/signin/chrome_device_id_helper.cc
index c433a4b3..ffe0f43 100644
--- a/chrome/browser/signin/chrome_device_id_helper.cc
+++ b/chrome/browser/signin/chrome_device_id_helper.cc
@@ -12,8 +12,8 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "base/command_line.h"
-#include "base/guid.h"
 #include "base/logging.h"
+#include "base/uuid.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/browser_process.h"
 #include "components/prefs/pref_service.h"
@@ -54,7 +54,7 @@
 
 std::string GenerateSigninScopedDeviceId(bool for_ephemeral) {
   constexpr char kEphemeralUserDeviceIDPrefix[] = "t_";
-  std::string guid = base::GenerateGUID();
+  std::string guid = base::Uuid::GenerateRandomV4().AsLowercaseString();
   return for_ephemeral ? kEphemeralUserDeviceIDPrefix + guid : guid;
 }
 
diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc
index 889f03cf..443da18 100644
--- a/chrome/browser/sync/sync_ui_util.cc
+++ b/chrome/browser/sync/sync_ui_util.cc
@@ -103,7 +103,8 @@
 
   // Check to see if sync has been disabled via the dashboard and needs to be
   // set up once again.
-  if (!service->GetUserSettings()->IsSyncRequested()) {
+  if (service->GetDisableReasons().Has(
+          syncer::SyncService::DISABLE_REASON_USER_CHOICE)) {
     return {SyncStatusMessageType::kSyncError,
             IDS_SIGNED_IN_WITH_SYNC_STOPPED_VIA_DASHBOARD,
             IDS_SETTINGS_EMPTY_STRING, SyncStatusActionType::kNoAction};
diff --git a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.cc b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.cc
index 4b753591..46ad8dc 100644
--- a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.cc
+++ b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.cc
@@ -70,9 +70,9 @@
     return {TriggerOutcome::kUnsupportedFieldType, {}};
   }
 
-  // Trigger only for complete forms (contining the fields for the card number
+  // Trigger only for complete forms (containing the fields for the card number
   // and the card expiration date).
-  if (!FormHasAllEmtyCreditCardFields(*form)) {
+  if (!FormHasAllCreditCardFields(*form)) {
     return {TriggerOutcome::kIncompleteForm, {}};
   }
   if (optional_received_form != nullptr &&
@@ -284,16 +284,14 @@
 }
 
 bool TouchToFillDelegateAndroidImpl::IsFormPrefilled(const FormData& form) {
-  return base::ranges::any_of(
-      form.fields.begin(), form.fields.end(), [&](const FormFieldData& field) {
-        AutofillField* autofill_field = manager_->GetAutofillField(form, field);
-        if (!autofill_field->HasExpirationDateType() &&
-            autofill_field->Type().GetStorableType() !=
-                ServerFieldType::CREDIT_CARD_NUMBER) {
-          return false;
-        }
-        return !SanitizedFieldIsEmpty(field.value);
-      });
+  return base::ranges::any_of(form.fields, [&](const FormFieldData& field) {
+    AutofillField* autofill_field = manager_->GetAutofillField(form, field);
+    if (autofill_field->Type().GetStorableType() !=
+        ServerFieldType::CREDIT_CARD_NUMBER) {
+      return false;
+    }
+    return !SanitizedFieldIsEmpty(field.value);
+  });
 }
 
 base::WeakPtr<TouchToFillDelegateAndroidImpl>
diff --git a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.h b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.h
index a3b3c28..10d04c71 100644
--- a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.h
+++ b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl.h
@@ -177,8 +177,9 @@
   bool IsFillingCorrect(const FormStructure& submitted_form) const;
 
   // Checks if the credit card form is already filled with values. The form is
-  // considered to be filled if the credit card number and the expiry date
-  // fields are non-empty.
+  // considered to be filled if the credit card number field is non-empty. The
+  // expiration date fields are not checked because they might have arbitrary
+  // placeholders.
   // TODO(crbug.com/1331312): FormData is used here to ensure that we check the
   // most recent form values. FormStructure knows only about the initial values.
   bool IsFormPrefilled(const FormData& form);
diff --git a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl_unittest.cc b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl_unittest.cc
index 126cca72..5a12284a 100644
--- a/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl_unittest.cc
+++ b/chrome/browser/touch_to_fill/payments/android/touch_to_fill_delegate_android_impl_unittest.cc
@@ -294,7 +294,7 @@
 }
 
 TEST_F(TouchToFillDelegateAndroidImplUnitTest,
-       TryToShowTouchToFillFailsForPrefilledYear) {
+       TryToShowTouchToFillSucceedsForPrefilledYear) {
   // Force the form to be parsed here to test the case, when form values are
   // changed after the form is added to the cache.
   browser_autofill_manager_->OnFormsSeen({form_}, {});
@@ -304,11 +304,11 @@
   form_.fields[3].value = u"2023";
   ASSERT_FALSE(touch_to_fill_delegate_->IsShowingTouchToFill());
 
-  TryToShowTouchToFill(/*expected_success=*/false);
+  TryToShowTouchToFill(/*expected_success=*/true);
 
   histogram_tester_.ExpectUniqueSample(
       kUmaTouchToFillCreditCardTriggerOutcome,
-      TouchToFillCreditCardTriggerOutcome::kFormAlreadyFilled, 1);
+      TouchToFillCreditCardTriggerOutcome::kShown, 1);
 }
 
 TEST_F(TouchToFillDelegateAndroidImplUnitTest,
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_details_view.cc b/chrome/browser/ui/views/passwords/manage_passwords_details_view.cc
index 0bc5a953..9dcd85f 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_details_view.cc
+++ b/chrome/browser/ui/views/passwords/manage_passwords_details_view.cc
@@ -347,10 +347,17 @@
     base::RepeatingClosure on_back_clicked_callback) {
   ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
   auto header = std::make_unique<views::BoxLayoutView>();
-  // Set the space between the icons and title similar to the default behavior
-  // in BubbleFrameView::Layout().
+  // Set the space between the icon and title similar to the space in the row
+  // below to make sure the bubble title and the labels below are vertically
+  // aligned. In the rows below the distance between the icon and the text is
+  // DISTANCE_RELATED_CONTROL_HORIZONTAL. But the icon in the title has a border
+  // of size INSETS_VECTOR_IMAGE_BUTTON to have space for the focus ring, and
+  // hence this is subtracted here.
   header->SetBetweenChildSpacing(
-      layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).left());
+      layout_provider->GetDistanceMetric(
+          views::DISTANCE_RELATED_CONTROL_HORIZONTAL) -
+      layout_provider->GetInsetsMetric(views::INSETS_VECTOR_IMAGE_BUTTON)
+          .right());
 
   auto back_button = views::CreateVectorImageButtonWithNativeTheme(
       on_back_clicked_callback, vector_icons::kArrowBackIcon);
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
index 2c059e1..d2f21d2ce 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -1624,7 +1624,7 @@
 
   // Sync is disabled.
   EXPECT_NE(entry->GetGAIAId(), std::string());
-  EXPECT_FALSE(sync_service->GetUserSettings()->IsSyncRequested());
+  EXPECT_FALSE(sync_service->IsSyncFeatureEnabled());
   EXPECT_EQ(ThemeServiceFactory::GetForProfile(profile_being_created)
                 ->GetAutogeneratedThemeColor(),
             kProfileColor);
diff --git a/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc b/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
index aa4aea82..b608c590 100644
--- a/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
+++ b/chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.cc
@@ -136,7 +136,8 @@
   auto url_info = IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(
       web_package::SignedWebBundleId::CreateRandomForDevelopment());
   WebAppProvider::GetForWebApps(profile)->scheduler().InstallIsolatedWebApp(
-      url_info, DevModeProxy{.proxy_url = proxy_origin}, future.GetCallback());
+      url_info, DevModeProxy{.proxy_url = proxy_origin}, /*keep_alive=*/nullptr,
+      /*profile_keep_alive=*/nullptr, future.GetCallback());
 
   CHECK(future.Get().has_value()) << future.Get().error();
 
diff --git a/chrome/browser/ui/web_applications/web_app_launch_utils.cc b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
index 33b440b5..bb4ec43 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_utils.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_utils.cc
@@ -492,8 +492,8 @@
     // TODO(crbug.com/1425284): Cover other app launch paths (e.g. restore
     // apps).
     auto partition_config = content::StoragePartitionConfig::Create(
-        nav_params.browser->profile(), /*partition_domain=*/app_id,
-        /*partition_name=*/"goldfish", /*in_memory=*/false);
+        nav_params.browser->profile(), /*partition_domain=*/"goldfish",
+        /*partition_name=*/app_id, /*in_memory=*/false);
 
     auto guest_site_instance = content::SiteInstance::CreateForGuest(
         nav_params.browser->profile(), partition_config);
diff --git a/chrome/browser/ui/webui/BUILD.gn b/chrome/browser/ui/webui/BUILD.gn
index 0ded0dc..10284775 100644
--- a/chrome/browser/ui/webui/BUILD.gn
+++ b/chrome/browser/ui/webui/BUILD.gn
@@ -64,6 +64,7 @@
       "//ash/webui/face_ml_app_ui",
       "//ash/webui/file_manager:file_manager_untrusted_ui",
       "//ash/webui/firmware_update_ui:firmware_update_ui",
+      "//ash/webui/guest_os_installer:guest_os_installer",
       "//ash/webui/help_app_ui",
       "//ash/webui/os_feedback_ui",
       "//ash/webui/shortcut_customization_ui",
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index 728a8ee2..3c7e963 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -350,6 +350,11 @@
 const DisplayScaleFactor k4KDisplay = {3840, 1.5f},
                          kMediumDisplay = {1440, 4.f / 3};
 
+bool OobeUIConfig::IsWebUIEnabled(content::BrowserContext* browser_context) {
+  return ash::ProfileHelper::IsSigninProfile(
+      Profile::FromBrowserContext(browser_context));
+}
+
 // static
 const char OobeUI::kAppLaunchSplashDisplay[] = "app-launch-splash";
 const char OobeUI::kGaiaSigninDisplay[] = "gaia-signin";
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.h b/chrome/browser/ui/webui/ash/login/oobe_ui.h
index f133fbc..b6ca84a 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.h
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.h
@@ -10,16 +10,19 @@
 #include <string>
 #include <vector>
 
+#include "ash/webui/common/chrome_os_webui_config.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/oobe_screen.h"
 #include "chrome/browser/ui/webui/ash/login/base_screen_handler.h"
 #include "chrome/browser/ui/webui/ash/login/core_oobe_handler.h"
+#include "chrome/common/webui_url_constants.h"
 #include "chromeos/ash/services/auth_factor_config/public/mojom/auth_factor_config.mojom-forward.h"
 #include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom-forward.h"
 #include "chromeos/ash/services/multidevice_setup/public/mojom/multidevice_setup.mojom-forward.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-forward.h"
+#include "content/public/common/url_constants.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 #include "ui/webui/resources/cr_components/color_change_listener/color_change_listener.mojom.h"
@@ -37,6 +40,17 @@
 class ErrorScreen;
 class NetworkStateInformer;
 class OobeDisplayChooser;
+class OobeUI;
+
+// The WebUIConfig for chrome://oobe urls
+class OobeUIConfig : public ChromeOSWebUIConfig<OobeUI> {
+ public:
+  OobeUIConfig()
+      : ChromeOSWebUIConfig(content::kChromeUIScheme,
+                            chrome::kChromeUIOobeHost) {}
+
+  bool IsWebUIEnabled(content::BrowserContext* browser_context) override;
+};
 
 // A custom WebUI that defines datasource for out-of-box-experience (OOBE) UI:
 // - welcome screen (setup language/keyboard/network).
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 9f1dfc0..fda8ec73 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -201,8 +201,6 @@
 #include "ash/webui/files_internals/url_constants.h"
 #include "ash/webui/firmware_update_ui/firmware_update_app_ui.h"
 #include "ash/webui/firmware_update_ui/url_constants.h"
-#include "ash/webui/guest_os_installer/guest_os_installer_ui.h"
-#include "ash/webui/guest_os_installer/url_constants.h"
 #include "ash/webui/help_app_ui/help_app_ui.h"
 #include "ash/webui/help_app_ui/url_constants.h"
 #include "ash/webui/media_app_ui/media_app_ui.h"
@@ -231,7 +229,6 @@
 #include "chrome/browser/ash/device_sync/device_sync_client_factory.h"
 #include "chrome/browser/ash/eche_app/eche_app_manager_factory.h"
 #include "chrome/browser/ash/extensions/url_constants.h"
-#include "chrome/browser/ash/guest_os/public/installer_delegate_factory.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service.h"
 #include "chrome/browser/ash/login/easy_unlock/easy_unlock_service_factory.h"
 #include "chrome/browser/ash/login/login_pref_names.h"
@@ -282,7 +279,6 @@
 #include "chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_ui.h"
 #include "chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.h"
 #include "chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.h"
-#include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/ash/manage_mirrorsync/manage_mirrorsync_ui.h"
 #include "chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_ui.h"
 #include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h"
@@ -500,11 +496,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 template <>
-WebUIController* NewWebUI<ash::OobeUI>(WebUI* web_ui, const GURL& url) {
-  return new ash::OobeUI(web_ui, url);
-}
-
-template <>
 WebUIController* NewWebUI<ash::TrustedProjectorUI>(WebUI* web_ui,
                                                    const GURL& url) {
   return new ash::TrustedProjectorUI(web_ui, url,
@@ -696,13 +687,6 @@
   return ash::personalization_app::CreatePersonalizationAppUI(web_ui);
 }
 
-template <>
-WebUIController* NewWebUI<ash::GuestOSInstallerUI>(WebUI* web_ui,
-                                                   const GURL& url) {
-  return new ash::GuestOSInstallerUI(
-      web_ui, url, base::BindRepeating(&guest_os::InstallerDelegateFactory));
-}
-
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
@@ -964,18 +948,10 @@
     return &NewComponentUI<ash::file_manager::FileManagerUI,
                            ChromeFileManagerUIDelegate>;
   }
-  if (url.host_piece() == ash::kChromeUIGuestOSInstallerHost)
-    return &NewWebUI<ash::GuestOSInstallerUI>;
   if (url.host_piece() == ash::kChromeUIHelpAppHost)
     return &NewComponentUI<ash::HelpAppUI, ash::ChromeHelpAppUIDelegate>;
   if (url.host_piece() == chrome::kChromeUIMobileSetupHost)
     return &NewWebUI<ash::cellular_setup::MobileSetupUI>;
-  if (url.host_piece() == chrome::kChromeUIOobeHost) {
-    if (ash::ProfileHelper::IsSigninProfile(profile)) {
-      return &NewWebUI<ash::OobeUI>;
-    }
-    return nullptr;
-  }
   if (url.host_piece() == ash::kChromeUIDiagnosticsAppHost) {
     return &NewWebUI<ash::DiagnosticsDialogUI>;
   }
diff --git a/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc b/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
index e068305d..c34a4a2 100644
--- a/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
+++ b/chrome/browser/ui/webui/chromeos/chrome_web_ui_configs_chromeos.cc
@@ -23,9 +23,11 @@
 #include "ash/webui/connectivity_diagnostics/connectivity_diagnostics_ui.h"
 #include "ash/webui/files_internals/files_internals_ui.h"
 #include "ash/webui/firmware_update_ui/firmware_update_app_ui.h"
+#include "ash/webui/guest_os_installer/guest_os_installer_ui.h"
 #include "ash/webui/os_feedback_ui/os_feedback_ui.h"
 #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
 #include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h"
+#include "chrome/browser/ash/guest_os/public/installer_delegate_factory.h"
 #include "chrome/browser/ash/net/network_health/network_health_manager.h"
 #include "chrome/browser/ash/os_feedback/chrome_os_feedback_delegate.h"
 #include "chrome/browser/ash/web_applications/camera_app/chrome_camera_app_ui_delegate.h"
@@ -55,6 +57,7 @@
 #include "chrome/browser/ui/webui/ash/launcher_internals/launcher_internals_ui.h"
 #include "chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_network_ui.h"
 #include "chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.h"
+#include "chrome/browser/ui/webui/ash/login/oobe_ui.h"
 #include "chrome/browser/ui/webui/ash/manage_mirrorsync/manage_mirrorsync_ui.h"
 #include "chrome/browser/ui/webui/ash/multidevice_internals/multidevice_internals_ui.h"
 #include "chrome/browser/ui/webui/ash/multidevice_setup/multidevice_setup_dialog.h"
@@ -138,6 +141,18 @@
       create_controller_func);
 }
 
+std::unique_ptr<content::WebUIConfig> MakeGuestOSInstallerUIConfig() {
+  CreateWebUIControllerFunc create_controller_func =
+      [](content::WebUI* web_ui,
+         const GURL& url) -> std::unique_ptr<content::WebUIController> {
+    return std::make_unique<ash::GuestOSInstallerUI>(
+        web_ui, base::BindRepeating(&guest_os::InstallerDelegateFactory));
+  };
+
+  return std::make_unique<ash::GuestOSInstallerUIConfig>(
+      create_controller_func);
+}
+
 void RegisterAshChromeWebUIConfigs() {
   // Add `WebUIConfig`s for Ash ChromeOS to the list here.
   auto& map = content::WebUIConfigMap::GetInstance();
@@ -170,6 +185,7 @@
       MakeComponentConfig<ash::FilesInternalsUIConfig, ash::FilesInternalsUI,
                           ChromeFilesInternalsUIDelegate>());
   map.AddWebUIConfig(std::make_unique<ash::FirmwareUpdateAppUIConfig>());
+  map.AddWebUIConfig(MakeGuestOSInstallerUIConfig());
   map.AddWebUIConfig(std::make_unique<ash::HealthdInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::HumanPresenceInternalsUIConfig>());
   map.AddWebUIConfig(std::make_unique<ash::InternetConfigDialogUIConfig>());
@@ -188,6 +204,7 @@
   map.AddWebUIConfig(std::make_unique<ash::NotificationTesterUIConfig>());
   map.AddWebUIConfig(
       std::make_unique<ash::office_fallback::OfficeFallbackUIConfig>());
+  map.AddWebUIConfig(std::make_unique<ash::OobeUIConfig>());
   map.AddWebUIConfig(
       MakeComponentConfig<ash::OSFeedbackUIConfig, ash::OSFeedbackUI,
                           ash::ChromeOsFeedbackDelegate>());
diff --git a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
index a6b8476..fdb57d76 100644
--- a/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
+++ b/chrome/browser/ui/webui/password_manager/password_manager_ui.cc
@@ -30,6 +30,7 @@
 #include "components/password_manager/content/common/web_ui_constants.h"
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 #include "components/password_manager/core/common/password_manager_constants.h"
+#include "components/password_manager/core/common/password_manager_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/base/features.h"
 #include "content/public/browser/url_data_source.h"
@@ -193,6 +194,8 @@
     {"importPasswordsSuccessTitle",
      IDS_PASSWORD_MANAGER_UI_IMPORT_SUCCESS_TITLE},
     {"importPasswordsSuccessTip", IDS_PASSWORD_MANAGER_UI_IMPORT_SUCCESS_TIP},
+    {"importPasswordsDeleteFileOption",
+     IDS_PASSWORD_MANAGER_UI_IMPORT_DELETE_FILE_OPTION},
     {"importPasswordsDescriptionAccount",
      IDS_PASSWORD_MANAGER_UI_IMPORT_DESCRIPTION_SYNCING_USERS},
     {"importPasswordsSelectFile",
@@ -321,6 +324,10 @@
                              g_browser_process->local_state()));
 #endif
 
+  source->AddBoolean("enablePasswordsImportM2",
+                     base::FeatureList::IsEnabled(
+                         password_manager::features::kPasswordsImportM2));
+
   source->AddString("passwordManagerLearnMoreURL",
                     chrome::kPasswordManagerLearnMoreURL);
 
diff --git a/chrome/browser/ui/webui/password_manager/sync_handler.cc b/chrome/browser/ui/webui/password_manager/sync_handler.cc
index 83af939..b6d1d154 100644
--- a/chrome/browser/ui/webui/password_manager/sync_handler.cc
+++ b/chrome/browser/ui/webui/password_manager/sync_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/password_manager/sync_handler.h"
 
+#include "base/values.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
@@ -84,6 +85,12 @@
   base::Value::Dict dict;
 
   syncer::SyncService* sync_service = GetSyncService();
+  // sync_service might be nullptr if SyncServiceFactory::IsSyncAllowed is
+  // false.
+  if (!sync_service) {
+    return dict;
+  }
+
   PrefService* pref_service = profile_->GetPrefs();
   syncer::UserSelectableTypeSet types =
       sync_service->GetUserSettings()->GetSelectedTypes();
diff --git a/chrome/browser/ui/webui/password_manager/sync_handler_unittest.cc b/chrome/browser/ui/webui/password_manager/sync_handler_unittest.cc
index 8ccbb98..80023bd 100644
--- a/chrome/browser/ui/webui/password_manager/sync_handler_unittest.cc
+++ b/chrome/browser/ui/webui/password_manager/sync_handler_unittest.cc
@@ -69,8 +69,6 @@
     auto account_info = identity_test_env()->MakePrimaryAccountAvailable(
         "user@gmail.com", signin::ConsentLevel::kSync);
     ON_CALL(*sync_service(), HasSyncConsent).WillByDefault(Return(true));
-    ON_CALL(*sync_service()->GetMockUserSettings(), IsSyncRequested)
-        .WillByDefault(Return(true));
     ON_CALL(*sync_service()->GetMockUserSettings(), IsFirstSetupComplete())
         .WillByDefault(Return(true));
     ON_CALL(*sync_service(), GetAccountInfo)
diff --git a/chrome/browser/ui/webui/settings/ash/internet_section.cc b/chrome/browser/ui/webui/settings/ash/internet_section.cc
index 073b8040..0474b14c 100644
--- a/chrome/browser/ui/webui/settings/ash/internet_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/internet_section.cc
@@ -996,7 +996,21 @@
       {"hotspotConfigNotLoginErrorMessage",
        IDS_SETTINGS_INTERNET_HOTSPOT_CONFIG_NOT_LOGIN_ERROR_MESSAGE},
       {"passpointProviderLabel", IDS_SETTINGS_INTERNET_PASSPOINT_PROVIDER},
+      {"passpointRemoveButton",
+       IDS_SETTINGS_INTERNET_PASSPOINT_REMOVE_SUBSCRIPTION},
       {"passpointSectionLabel", IDS_SETTINGS_INTERNET_PASSPOINT_SECTION_LABEL},
+      {"passpointHeadlineText", IDS_SETTINGS_INTERNET_PASSPOINT_HEADLINE},
+      {"passpointSubscriptionExpirationLabel",
+       IDS_SETTINGS_INTERNET_PASSPOINT_SUBSCRIPTION_EXPIRATION},
+      {"passpointSourceLabel", IDS_SETTINGS_INTERNET_PASSPOINT_SOURCE},
+      {"passpointTrustedCALabel", IDS_SETTINGS_INTERNET_PASSPOINT_TRUSTED_CA},
+      {"passpointSystemCALabel", IDS_SETTINGS_INTERNET_PASSPOINT_SYSTEM_CA},
+      {"passpointDomainsLabel", IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS},
+      {"passpointDomainsA11yLabel",
+       IDS_SETTINGS_INTERNET_PASSPOINT_DOMAINS_A11Y_LABEL},
+      {"passpointRemovalTitle", IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_TITLE},
+      {"passpointRemovalDescription",
+       IDS_SETTINGS_INTERNET_PASSPOINT_REMOVAL_DESCRIPTION},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
index 1fc58dde..93f8179 100644
--- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -266,8 +266,6 @@
   void SetDefaultExpectationsForConfigPage() {
     ON_CALL(*mock_sync_service_, GetDisableReasons())
         .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-    ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-        .WillByDefault(Return(true));
     ON_CALL(*mock_sync_service_->GetMockUserSettings(),
             GetRegisteredSelectableTypes())
         .WillByDefault(Return(GetAllTypes()));
@@ -413,8 +411,6 @@
   CreatePeopleHandler();
   ON_CALL(*mock_sync_service_, GetDisableReasons())
       .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-      .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsFirstSetupComplete())
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_, GetTransportState())
@@ -450,8 +446,6 @@
       .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_, GetDisableReasons())
       .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-      .WillByDefault(Return(true));
   // Sync engine is stopped initially, and will start up.
   ON_CALL(*mock_sync_service_, GetTransportState())
       .WillByDefault(
@@ -490,8 +484,6 @@
   CreatePeopleHandler();
   ON_CALL(*mock_sync_service_, GetDisableReasons())
       .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-      .WillByDefault(Return(true));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsFirstSetupComplete())
       .WillByDefault(Return(false));
   EXPECT_CALL(*mock_sync_service_, GetTransportState())
@@ -533,8 +525,6 @@
         // immediately starts initializing the engine.
         ON_CALL(*mock_sync_service_, GetDisableReasons())
             .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-        ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-            .WillByDefault(Return(true));
         ON_CALL(*mock_sync_service_, GetTransportState())
             .WillByDefault(
                 Return(syncer::SyncService::TransportState::INITIALIZING));
@@ -565,8 +555,6 @@
         // engine is already running, it just gets reconfigured.
         ON_CALL(*mock_sync_service_, GetDisableReasons())
             .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-        ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-            .WillByDefault(Return(true));
         ON_CALL(*mock_sync_service_, GetTransportState())
             .WillByDefault(
                 Return(syncer::SyncService::TransportState::CONFIGURING));
@@ -1132,8 +1120,6 @@
   // bits being cleared.
   ON_CALL(*mock_sync_service_, GetDisableReasons())
       .WillByDefault(Return(syncer::SyncService::DISABLE_REASON_USER_CHOICE));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-      .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsFirstSetupComplete())
       .WillByDefault(Return(false));
   // Sync will eventually start again in transport mode.
@@ -1151,8 +1137,6 @@
         // immediately starts initializing the engine.
         ON_CALL(*mock_sync_service_, GetDisableReasons())
             .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-        ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-            .WillByDefault(Return(true));
         ON_CALL(*mock_sync_service_, GetTransportState())
             .WillByDefault(
                 Return(syncer::SyncService::TransportState::INITIALIZING));
@@ -1186,8 +1170,6 @@
   // bits being cleared.
   ON_CALL(*mock_sync_service_, GetDisableReasons())
       .WillByDefault(Return(syncer::SyncService::DISABLE_REASON_USER_CHOICE));
-  ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-      .WillByDefault(Return(false));
   ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsFirstSetupComplete())
       .WillByDefault(Return(false));
   // Sync will eventually start again in transport mode.
@@ -1218,8 +1200,6 @@
         // immediately starts initializing the engine.
         ON_CALL(*mock_sync_service_, GetDisableReasons())
             .WillByDefault(Return(syncer::SyncService::DisableReasonSet()));
-        ON_CALL(*mock_sync_service_->GetMockUserSettings(), IsSyncRequested())
-            .WillByDefault(Return(true));
         ON_CALL(*mock_sync_service_, GetTransportState())
             .WillByDefault(
                 Return(syncer::SyncService::TransportState::INITIALIZING));
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 7b60268..4d7be92 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
@@ -76,20 +76,6 @@
   return dest;
 }
 
-std::unique_ptr<IsolatedWebAppResponseReaderFactory>
-CreateDefaultResponseReaderFactory(content::BrowserContext& browser_context) {
-  Profile& profile = *Profile::FromBrowserContext(&browser_context);
-  PrefService& pref_service = *profile.GetPrefs();
-
-  auto trust_checker =
-      std::make_unique<IsolatedWebAppTrustChecker>(pref_service);
-  auto validator =
-      std::make_unique<IsolatedWebAppValidator>(std::move(trust_checker));
-
-  return std::make_unique<IsolatedWebAppResponseReaderFactory>(
-      std::move(validator));
-}
-
 }  // namespace
 
 InstallIsolatedWebAppCommand::InstallIsolatedWebAppCommand(
@@ -97,25 +83,8 @@
     const IsolatedWebAppLocation& location,
     std::unique_ptr<content::WebContents> web_contents,
     std::unique_ptr<WebAppUrlLoader> url_loader,
-    content::BrowserContext& browser_context,
-    base::OnceCallback<void(base::expected<InstallIsolatedWebAppCommandSuccess,
-                                           InstallIsolatedWebAppCommandError>)>
-        callback)
-    : InstallIsolatedWebAppCommand(
-          url_info,
-          location,
-          std::move(web_contents),
-          std::move(url_loader),
-          browser_context,
-          std::move(callback),
-          CreateDefaultResponseReaderFactory(browser_context)) {}
-
-InstallIsolatedWebAppCommand::InstallIsolatedWebAppCommand(
-    const IsolatedWebAppUrlInfo& url_info,
-    const IsolatedWebAppLocation& location,
-    std::unique_ptr<content::WebContents> web_contents,
-    std::unique_ptr<WebAppUrlLoader> url_loader,
-    content::BrowserContext& browser_context,
+    std::unique_ptr<ScopedKeepAlive> keep_alive,
+    std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
     base::OnceCallback<void(base::expected<InstallIsolatedWebAppCommandSuccess,
                                            InstallIsolatedWebAppCommandError>)>
         callback,
@@ -129,12 +98,15 @@
       response_reader_factory_(std::move(response_reader_factory)),
       web_contents_(std::move(web_contents)),
       url_loader_(std::move(url_loader)),
-      browser_context_(browser_context),
+      keep_alive_(std::move(keep_alive)),
+      profile_keep_alive_(std::move(profile_keep_alive)),
       data_retriever_(std::make_unique<WebAppDataRetriever>()) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
 
-  DCHECK(web_contents_ != nullptr);
-  DCHECK(!callback.is_null());
+  CHECK(web_contents_ != nullptr);
+  CHECK(!callback.is_null());
+  CHECK(profile_keep_alive_ == nullptr ||
+        &profile() == profile_keep_alive_->profile());
 
   callback_ =
       base::BindOnce(
@@ -146,6 +118,18 @@
           .Then(std::move(callback));
 }
 
+// static
+std::unique_ptr<IsolatedWebAppResponseReaderFactory>
+InstallIsolatedWebAppCommand::CreateDefaultResponseReaderFactory(
+    const PrefService& prefs) {
+  auto trust_checker = std::make_unique<IsolatedWebAppTrustChecker>(prefs);
+  auto validator =
+      std::make_unique<IsolatedWebAppValidator>(std::move(trust_checker));
+
+  return std::make_unique<IsolatedWebAppResponseReaderFactory>(
+      std::move(validator));
+}
+
 void InstallIsolatedWebAppCommand::SetDataRetrieverForTesting(
     std::unique_ptr<WebAppDataRetriever> data_retriever) {
   data_retriever_ = std::move(data_retriever);
@@ -172,8 +156,6 @@
     std::unique_ptr<AppLock> lock) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   lock_ = std::move(lock);
-  const PrefService& prefs =
-      *Profile::FromBrowserContext(&*browser_context_)->GetPrefs();
 
   absl::visit(
       base::Overloaded{
@@ -185,7 +167,7 @@
           [&](const DevModeBundle& location) {
             DCHECK_EQ(url_info_.web_bundle_id().type(),
                       web_package::SignedWebBundleId::Type::kEd25519PublicKey);
-            if (!IsIwaDevModeEnabled(prefs)) {
+            if (!IsIwaDevModeEnabled(prefs())) {
               ReportFailure(kIwaDevModeNotEnabledMessage);
               return;
             }
@@ -194,7 +176,7 @@
           [&](const DevModeProxy& location) {
             DCHECK_EQ(url_info_.web_bundle_id().type(),
                       web_package::SignedWebBundleId::Type::kDevelopment);
-            if (!IsIwaDevModeEnabled(prefs)) {
+            if (!IsIwaDevModeEnabled(prefs())) {
               ReportFailure(kIwaDevModeNotEnabledMessage);
               return;
             }
@@ -251,9 +233,8 @@
 }
 
 void InstallIsolatedWebAppCommand::CreateStoragePartition() {
-  browser_context_->GetStoragePartition(
-      url_info_.storage_partition_config(&*browser_context_),
-      /*can_create=*/true);
+  profile().GetStoragePartition(url_info_.storage_partition_config(&profile()),
+                                /*can_create=*/true);
 }
 
 void InstallIsolatedWebAppCommand::LoadUrl() {
@@ -478,4 +459,14 @@
                      InstallIsolatedWebAppCommandSuccess{}));
 }
 
+Profile& InstallIsolatedWebAppCommand::profile() {
+  CHECK(web_contents_);
+  CHECK(web_contents_->GetBrowserContext());
+  return *Profile::FromBrowserContext(web_contents_->GetBrowserContext());
+}
+
+const PrefService& InstallIsolatedWebAppCommand::prefs() {
+  return *profile().GetPrefs();
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h
index 74ded11..b1e8292d 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h
@@ -16,6 +16,7 @@
 #include "base/strings/string_piece_forward.h"
 #include "base/types/expected.h"
 #include "base/values.h"
+#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
 #include "chrome/browser/web_applications/commands/web_app_command.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_location.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_response_reader_factory.h"
@@ -23,14 +24,16 @@
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app_id.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
 #include "components/webapps/browser/install_result_code.h"
 #include "components/webapps/browser/installable/installable_logging.h"
 #include "third_party/blink/public/mojom/manifest/manifest.mojom-forward.h"
 
 class GURL;
+class Profile;
+class PrefService;
 
 namespace content {
-class BrowserContext;
 class WebContents;
 }  // namespace content
 
@@ -64,35 +67,30 @@
 // re-using web contents.
 class InstallIsolatedWebAppCommand : public WebAppCommandTemplate<AppLock> {
  public:
-  //
-  // |url_info| holds the origin information of the app. It is
-  // randomly generated for dev-proxy and the public key of signed bundle. It is
+  static std::unique_ptr<IsolatedWebAppResponseReaderFactory>
+  CreateDefaultResponseReaderFactory(const PrefService& prefs);
+
+  // |url_info| holds the origin information of the app. It is randomly
+  // generated for dev-proxy and the public key of signed bundle. It is
   // guarantee to be valid.
   //
-  // |location| holds information about the
-  // mode(dev-mod-proxy/signed-bundle) and the source.
+  // |location| holds information about the mode(dev-mod-proxy/signed-bundle)
+  // and the source.
   //
   // |callback| must be not null.
   //
   // The `id` in the application's manifest must equal "/".
+  //
+  // `response_reader_factory` should be created via
+  // `CreateDefaultResponseReaderFactory` and is used to create the
+  // `IsolatedWebAppResponseReader` for the Web Bundle.
   explicit InstallIsolatedWebAppCommand(
       const IsolatedWebAppUrlInfo& url_info,
       const IsolatedWebAppLocation& location,
       std::unique_ptr<content::WebContents> web_contents,
       std::unique_ptr<WebAppUrlLoader> url_loader,
-      content::BrowserContext& browser_context,
-      base::OnceCallback<
-          void(base::expected<InstallIsolatedWebAppCommandSuccess,
-                              InstallIsolatedWebAppCommandError>)> callback);
-
-  // Same constructor as above, but additionally exposes the
-  // `response_reader_factory` for providing a mock factory in testing.
-  explicit InstallIsolatedWebAppCommand(
-      const IsolatedWebAppUrlInfo& url_info,
-      const IsolatedWebAppLocation& location,
-      std::unique_ptr<content::WebContents> web_contents,
-      std::unique_ptr<WebAppUrlLoader> url_loader,
-      content::BrowserContext& browser_context,
+      std::unique_ptr<ScopedKeepAlive> keep_alive,
+      std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
       base::OnceCallback<
           void(base::expected<InstallIsolatedWebAppCommandSuccess,
                               InstallIsolatedWebAppCommandError>)> callback,
@@ -123,6 +121,9 @@
   void ReportFailure(base::StringPiece message);
   void ReportSuccess();
 
+  Profile& profile();
+  const PrefService& prefs();
+
   void DownloadIcons(WebAppInstallInfo install_info);
   void OnGetIcons(WebAppInstallInfo install_info,
                   IconsDownloadedResult result,
@@ -166,7 +167,8 @@
 
   std::unique_ptr<WebAppUrlLoader> url_loader_;
 
-  base::raw_ref<content::BrowserContext> browser_context_;
+  std::unique_ptr<ScopedKeepAlive> keep_alive_;
+  std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
 
   std::unique_ptr<WebAppDataRetriever> data_retriever_;
 
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 303b7577..6546433 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
@@ -315,7 +315,8 @@
 
     return std::make_unique<InstallIsolatedWebAppCommand>(
         url_info, location.value(), std::move(web_contents),
-        std::move(url_loader), *profile(), std::move(callback),
+        std::move(url_loader), /*keep_alive=*/nullptr,
+        /*profile_keep_alive=*/nullptr, std::move(callback),
         std::make_unique<FakeResponseReaderFactory>(std::move(bundle_error)));
   }
 
diff --git a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc
index 7ceaa8d..68f4b7c 100644
--- a/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_from_command_line.cc
@@ -105,8 +105,11 @@
     return;
   }
 
+  // TODO(cmfcmf): Keep alives need to be set here.
   provider->scheduler().InstallIsolatedWebApp(
-      url_info.value(), location, base::BindOnce(&ReportInstallationResult));
+      url_info.value(), location, /*keep_alive=*/nullptr,
+      /*profile_keep_alive=*/nullptr,
+      base::BindOnce(&ReportInstallationResult));
 }
 
 base::expected<absl::optional<IsolatedWebAppLocation>, std::string>
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
index 754db423..5d26c268 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
@@ -51,8 +51,13 @@
     const IsolatedWebAppLocation& location,
     const IsolatedWebAppUrlInfo& url_info,
     WebAppCommandScheduler::InstallIsolatedWebAppCallback callback) {
-  provider_->scheduler().InstallIsolatedWebApp(url_info, location,
-                                               std::move(callback));
+  // There is no need to keep the browser or profile alive when
+  // policy-installing an IWA. If the browser or profile shut down, installation
+  // will be re-attempted the next time they start, assuming that the policy is
+  // still set.
+  provider_->scheduler().InstallIsolatedWebApp(
+      url_info, location, /*keep_alive=*/nullptr,
+      /*profile_keep_alive=*/nullptr, std::move(callback));
 }
 
 IsolatedWebAppPolicyManager::IsolatedWebAppPolicyManager(
diff --git a/chrome/browser/web_applications/policy/web_app_policy_constants.cc b/chrome/browser/web_applications/policy/web_app_policy_constants.cc
index 951613c..b3d79ce8 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_constants.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_constants.cc
@@ -26,5 +26,6 @@
 const char kAllowed[] = "allowed";
 const char kBlocked[] = "blocked";
 const char kRunWindowed[] = "run_windowed";
+const char kPreventClose[] = "prevent_close";
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/policy/web_app_policy_constants.h b/chrome/browser/web_applications/policy/web_app_policy_constants.h
index 7126474..692c83da 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_constants.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_constants.h
@@ -28,6 +28,7 @@
 extern const char kAllowed[];
 extern const char kBlocked[];
 extern const char kRunWindowed[];
+extern const char kPreventClose[];
 
 }  // namespace web_app
 
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager.cc b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
index 6a7e415..1626fe0 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.cc
@@ -654,6 +654,26 @@
     OverrideManifest(install_url, manifest);
 }
 
+bool WebAppPolicyManager::IsPreventCloseEnabled(const AppId& app_id) const {
+#if BUILDFLAG(IS_CHROMEOS)
+  if (!base::FeatureList::IsEnabled(
+          features::kDesktopPWAsEnforceWebAppSettingsPolicy) ||
+      !base::FeatureList::IsEnabled(features::kDesktopPWAsPreventClose)) {
+    return false;
+  }
+
+  const std::string unhashed_id =
+      app_registrar_->GetComputedUnhashedAppId(app_id);
+  auto it = settings_by_url_.find(unhashed_id);
+  if (it != settings_by_url_.end()) {
+    return it->second.prevent_close;
+  }
+  return default_settings_->prevent_close;
+#else
+  return false;
+#endif  // BUILDFLAG(IS_CHROMEOS)
+}
+
 void WebAppPolicyManager::OnAppsSynchronized(
     std::map<GURL, ExternallyManagedAppManager::InstallResult> install_results,
     std::map<GURL, bool> uninstall_results) {
@@ -693,11 +713,26 @@
     }
   }
 
+#if BUILDFLAG(IS_CHROMEOS)
+  // The value of "prevent_close" shall only be considered if run-on-os-login
+  // is enforced.
+  if (base::FeatureList::IsEnabled(
+          features::kDesktopPWAsEnforceWebAppSettingsPolicy) &&
+      base::FeatureList::IsEnabled(features::kDesktopPWAsPreventClose) &&
+      run_on_os_login_policy == RunOnOsLoginPolicy::kRunWindowed) {
+    absl::optional<bool> prevent_close_value = dict.FindBoolKey(kPreventClose);
+    if (prevent_close_value && *prevent_close_value) {
+      prevent_close = true;
+    }
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
   return true;
 }
 
 void WebAppPolicyManager::WebAppSetting::ResetSettings() {
   run_on_os_login_policy = RunOnOsLoginPolicy::kAllowed;
+  prevent_close = false;
 }
 
 WebAppPolicyManager::CustomManifestValues::CustomManifestValues() = default;
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 27418d18..ee8d083 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager.h
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager.h
@@ -107,6 +107,8 @@
   void MaybeOverrideManifest(content::RenderFrameHost* frame_host,
                              blink::mojom::ManifestPtr& manifest) const;
 
+  bool IsPreventCloseEnabled(const AppId& app_id) const;
+
  private:
   friend class WebAppPolicyManagerTest;
 
@@ -120,6 +122,7 @@
     void ResetSettings();
 
     RunOnOsLoginPolicy run_on_os_login_policy;
+    bool prevent_close;
   };
 
   struct CustomManifestValues {
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 cc8b071f..56960d7 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
@@ -313,6 +313,7 @@
 struct TestParam {
   TestLacrosParam lacros_params;
   test::ExternalPrefMigrationTestCases pref_migration_test_param;
+  bool prevent_close_enabled;
 };
 
 class WebAppPolicyManagerTest : public ChromeRenderViewHostTestHarness,
@@ -453,9 +454,24 @@
             features::kUseWebAppDBInsteadOfExternalPrefs);
         break;
     }
+
+    if (GetParam().prevent_close_enabled) {
+      enabled_features.push_back(
+          features::kDesktopPWAsEnforceWebAppSettingsPolicy);
+      enabled_features.push_back(features::kDesktopPWAsPreventClose);
+    }
+
     scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
+  void InstallPwa(const std::string& url) {
+    std::unique_ptr<WebAppInstallInfo> web_app_info =
+        std::make_unique<WebAppInstallInfo>();
+    web_app_info->start_url = GURL(url);
+    web_app_info->manifest_id = "";
+    web_app::test::InstallWebApp(profile(), std::move(web_app_info));
+  }
+
   bool ShouldSkipPWASpecificTest() {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     if (GetParam().lacros_params == TestLacrosParam::kLacrosEnabled) {
@@ -521,6 +537,11 @@
         unhashed_app_id);
   }
 
+  bool IsPreventCloseEnabled(const std::string& unhashed_app_id) {
+    return policy_manager().IsPreventCloseEnabled(
+        web_app::GenerateAppIdFromUnhashed(unhashed_app_id));
+  }
+
   void WaitForAppsToSynchronize() {
     base::RunLoop loop;
     policy_manager().SetOnAppsSynchronizedCompletedCallbackForTesting(
@@ -1391,6 +1412,61 @@
   app_registrar().RemoveObserver(&mock_observer);
 }
 
+TEST_P(WebAppPolicyManagerTest, WebAppSettingsPreventClose) {
+  if (ShouldSkipPWASpecificTest()) {
+    return;
+  }
+  const char kWebAppSettingNoDefaultConfiguration[] = R"([
+    {
+      "manifest_id": "https://windowed.example/",
+      "run_on_os_login": "run_windowed",
+      "prevent_close": true
+    },
+    {
+      "manifest_id": "https://tabbed.example/",
+      "run_on_os_login": "blocked",
+      "prevent_close": true
+    },
+    {
+      "manifest_id": "https://no-container.example/",
+      "run_on_os_login": "unsupported_value",
+      "prevent_close": true
+    },
+    {
+      "manifest_id": "bad.uri",
+      "run_on_os_login": "allowed",
+      "prevent_close": true
+    }
+  ])";
+
+  // Make sure that WebAppRegistrar::GetComputedUnhashedAppId does not fail.
+  InstallPwa(kWindowedUrl);
+  InstallPwa(kTabbedUrl);
+  InstallPwa(kNoContainerUrl);
+
+  base::RunLoop loop;
+  policy_manager().SetRefreshPolicySettingsCompletedCallbackForTesting(
+      loop.QuitClosure());
+  SetWebAppSettingsListPref(kWebAppSettingNoDefaultConfiguration);
+  loop.Run();
+
+#if BUILDFLAG(IS_CHROMEOS)
+  if (GetParam().prevent_close_enabled) {
+    EXPECT_TRUE(IsPreventCloseEnabled(kWindowedUrl));
+    EXPECT_FALSE(IsPreventCloseEnabled(kTabbedUrl));
+    EXPECT_FALSE(IsPreventCloseEnabled(kNoContainerUrl));
+  } else {
+    EXPECT_FALSE(IsPreventCloseEnabled(kWindowedUrl));
+    EXPECT_FALSE(IsPreventCloseEnabled(kTabbedUrl));
+    EXPECT_FALSE(IsPreventCloseEnabled(kNoContainerUrl));
+  }
+#else
+  EXPECT_FALSE(IsPreventCloseEnabled(kWindowedUrl));
+  EXPECT_FALSE(IsPreventCloseEnabled(kTabbedUrl));
+  EXPECT_FALSE(IsPreventCloseEnabled(kNoContainerUrl));
+#endif  // BUILDFLAG(IS_CHROMEOS)
+}
+
 INSTANTIATE_TEST_SUITE_P(
     WebAppPolicyManagerTestWithParams,
     WebAppPolicyManagerTest,
@@ -1398,29 +1474,67 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
         TestParam(
             {TestLacrosParam::kLacrosDisabled,
-             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref}),
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+             /*prevent_close_enabled=*/false}),
         TestParam(
             {TestLacrosParam::kLacrosDisabled,
-             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB}),
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+             /*prevent_close_enabled=*/false}),
         TestParam(
             {TestLacrosParam::kLacrosDisabled,
-             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref}),
-        TestParam(
-            {TestLacrosParam::kLacrosDisabled,
-             test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB}),
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+             /*prevent_close_enabled=*/false}),
+        TestParam({TestLacrosParam::kLacrosDisabled,
+                   test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB,
+                   /*prevent_close_enabled=*/false}),
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
         TestParam(
             {TestLacrosParam::kLacrosEnabled,
-             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref}),
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+             /*prevent_close_enabled=*/false}),
         TestParam(
             {TestLacrosParam::kLacrosEnabled,
-             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB}),
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+             /*prevent_close_enabled=*/false}),
         TestParam(
             {TestLacrosParam::kLacrosEnabled,
-             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref}),
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+             /*prevent_close_enabled=*/false}),
+        TestParam({TestLacrosParam::kLacrosEnabled,
+                   test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB,
+                   /*prevent_close_enabled=*/false}),
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+             /*prevent_close_enabled=*/true}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+             /*prevent_close_enabled=*/true}),
+        TestParam(
+            {TestLacrosParam::kLacrosDisabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+             /*prevent_close_enabled=*/true}),
+        TestParam({TestLacrosParam::kLacrosDisabled,
+                   test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB,
+                   /*prevent_close_enabled=*/true}),
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
         TestParam(
             {TestLacrosParam::kLacrosEnabled,
-             test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB})),
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadPref,
+             /*prevent_close_enabled=*/true}),
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kDisableMigrationReadDB,
+             /*prevent_close_enabled=*/true}),
+        TestParam(
+            {TestLacrosParam::kLacrosEnabled,
+             test::ExternalPrefMigrationTestCases::kEnableMigrationReadPref,
+             /*prevent_close_enabled=*/true}),
+        TestParam({TestLacrosParam::kLacrosEnabled,
+                   test::ExternalPrefMigrationTestCases::kEnableMigrationReadDB,
+                   /*prevent_close_enabled=*/true})),
     [](const ::testing::TestParamInfo<TestParam>& info) {
       std::string test_name = "Test_";
       if (info.param.lacros_params == TestLacrosParam::kLacrosEnabled)
@@ -1442,6 +1556,12 @@
           test_name.append("EnableMigration_ReadFromDB");
           break;
       }
+
+      if (info.param.prevent_close_enabled) {
+        test_name.append("PreventCloseEnabled");
+      } else {
+        test_name.append("PreventCloseDisabled");
+      }
       return test_name;
     });
 
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.cc b/chrome/browser/web_applications/web_app_command_scheduler.cc
index 3815a1fe..5c8f245 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.cc
+++ b/chrome/browser/web_applications/web_app_command_scheduler.cc
@@ -314,8 +314,13 @@
 void WebAppCommandScheduler::InstallIsolatedWebApp(
     const IsolatedWebAppUrlInfo& url_info,
     const IsolatedWebAppLocation& location,
+    std::unique_ptr<ScopedKeepAlive> keep_alive,
+    std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
     InstallIsolatedWebAppCallback callback,
     const base::Location& call_location) {
+  DCHECK(profile_keep_alive == nullptr ||
+         profile_keep_alive->profile() == &*profile_);
+
   if (IsShuttingDown()) {
     InstallIsolatedWebAppCommandError error;
     error.message = "The profile and/or browser are shutting down.";
@@ -328,7 +333,10 @@
   provider_->command_manager().ScheduleCommand(
       std::make_unique<InstallIsolatedWebAppCommand>(
           url_info, location, CreateIsolatedWebAppWebContents(*profile_),
-          std::make_unique<WebAppUrlLoader>(), *profile_, std::move(callback)),
+          std::make_unique<WebAppUrlLoader>(), std::move(keep_alive),
+          std::move(profile_keep_alive), std::move(callback),
+          InstallIsolatedWebAppCommand::CreateDefaultResponseReaderFactory(
+              *profile_->GetPrefs())),
       call_location);
 }
 
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.h b/chrome/browser/web_applications/web_app_command_scheduler.h
index 646e43d..d88fcc9 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.h
+++ b/chrome/browser/web_applications/web_app_command_scheduler.h
@@ -161,10 +161,13 @@
 
   // Schedules a command that installs the Isolated Web App described by the
   // given IsolatedWebAppUrlInfo and IsolationData.
-  void InstallIsolatedWebApp(const IsolatedWebAppUrlInfo& url_info,
-                             const IsolatedWebAppLocation& location,
-                             InstallIsolatedWebAppCallback callback,
-                             const base::Location& call_location = FROM_HERE);
+  void InstallIsolatedWebApp(
+      const IsolatedWebAppUrlInfo& url_info,
+      const IsolatedWebAppLocation& location,
+      std::unique_ptr<ScopedKeepAlive> keep_alive,
+      std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive,
+      InstallIsolatedWebAppCallback callback,
+      const base::Location& call_location = FROM_HERE);
 
   // Computes the browsing data size of all installed Isolated Web Apps.
   void GetIsolatedWebAppBrowsingData(
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index 377aa9c..e8b6f403 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1682048921-eeb52421ccc94b14aae3c77c91272ec82f749451.profdata
+chrome-chromeos-amd64-generic-main-1682078092-766d36b3038d584ad4f93080b116939d7457f2da.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 90158d7..7fa39581 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1682048921-b0abc953971a32f4c19d1286dfd19690a33d1656.profdata
+chrome-mac-arm-main-1682070945-4986223444ad51e35b587ce493edebba537cc33d.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 44567d6..7bcd943e 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1682034798-ee55d11b73f9a8fbf3196585be3c3fd21047507a.profdata
+chrome-mac-main-1682056724-4d44d1e17fd3ccff93441b862dc627e46af0dd5f.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 264ca88..5bb9536a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1682045784-8ab28d72f980bfa2955d71ec4bb020a2265861d8.profdata
+chrome-win32-main-1682067136-9335a3b3dcbdf943ef113711516cd79a2a4330ae.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index dd3ac82..906fb09 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1682045784-fe4aa10d2ec84b46d53d21afb8d4b9a1faf5a06e.profdata
+chrome-win64-main-1682067136-60e9e14529c0fdb21e7529dc476fd176458a1686.profdata
diff --git a/chrome/chrome_cleaner/settings/settings.cc b/chrome/chrome_cleaner/settings/settings.cc
index 7047746..bacd7de 100644
--- a/chrome/chrome_cleaner/settings/settings.cc
+++ b/chrome/chrome_cleaner/settings/settings.cc
@@ -10,10 +10,10 @@
 
 #include "base/command_line.h"
 #include "base/containers/cxx20_erase.h"
-#include "base/guid.h"
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
+#include "base/uuid.h"
 #include "base/win/win_util.h"
 #include "chrome/chrome_cleaner/buildflags.h"
 #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
@@ -121,7 +121,7 @@
 std::string GetCleanerRunId(const base::CommandLine& command_line) {
   std::string cleanup_id = command_line.GetSwitchValueASCII(kCleanupIdSwitch);
   if (cleanup_id.empty()) {
-    cleanup_id = base::GenerateGUID();
+    cleanup_id = base::Uuid::GenerateRandomV4().AsLowercaseString();
     DCHECK(!cleanup_id.empty());
   }
   return cleanup_id;
diff --git a/chrome/chrome_cleaner/test/test_native_reg_util.cc b/chrome/chrome_cleaner/test/test_native_reg_util.cc
index 8fc1459..4cf62bd 100644
--- a/chrome/chrome_cleaner/test/test_native_reg_util.cc
+++ b/chrome/chrome_cleaner/test/test_native_reg_util.cc
@@ -6,12 +6,12 @@
 
 #include <string>
 
-#include "base/guid.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/test_reg_util_win.h"
 #include "base/time/time.h"
+#include "base/uuid.h"
 #include "base/win/win_util.h"
 #include "chrome/chrome_cleaner/os/registry.h"
 
@@ -30,7 +30,8 @@
       base::Time::Now().ToDeltaSinceWindowsEpoch();
   key_path_ = base::StrCat(
       {kTempTestKeyPath, base::NumberToWString(timestamp.InMicroseconds()),
-       kTimestampDelimiter, base::ASCIIToWide(base::GenerateGUID())});
+       kTimestampDelimiter,
+       base::ASCIIToWide(base::Uuid::GenerateRandomV4().AsLowercaseString())});
 
   CHECK(ERROR_SUCCESS ==
         reg_key_.Create(HKEY_LOCAL_MACHINE, key_path_.c_str(), KEY_ALL_ACCESS));
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 532516b..6c2a44a 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2903,10 +2903,6 @@
 const char kBrowserShowProfilePickerOnStartup[] =
     "profile.show_picker_on_startup";
 
-// Boolean which indicates if the user is allowed to sign into Chrome on the
-// next startup.
-const char kSigninAllowedOnNextStartup[] = "signin.allowed_on_next_startup";
-
 // Boolean which indicate if signin interception is enabled.
 const char kSigninInterceptionEnabled[] = "signin.interception_enabled";
 
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 319dcae3..931cd84 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -973,7 +973,6 @@
 extern const char kBrowserProfilePickerAvailabilityOnStartup[];
 extern const char kBrowserProfilePickerShown[];
 extern const char kBrowserShowProfilePickerOnStartup[];
-extern const char kSigninAllowedOnNextStartup[];
 extern const char kSigninInterceptionEnabled[];
 #if BUILDFLAG(IS_CHROMEOS)
 extern const char kEchoCheckedOffers[];
diff --git a/chrome/test/data/webui/password_manager/passwords_importer_test.ts b/chrome/test/data/webui/password_manager/passwords_importer_test.ts
index 659adadd..9250126 100644
--- a/chrome/test/data/webui/password_manager/passwords_importer_test.ts
+++ b/chrome/test/data/webui/password_manager/passwords_importer_test.ts
@@ -59,18 +59,23 @@
   assertEquals(expectedText, element?.textContent!.trim());
 }
 
-function assertButtonShouldCloseDialog(
-    importer: PasswordsImporterElement, dialog: HTMLElement, selector: string) {
+async function closeDialogHelper(
+    importer: PasswordsImporterElement,
+    passwordManager: TestPasswordManagerProxy, dialog: HTMLElement,
+    selector: string, shouldDeleteFile: boolean = false) {
   const button = dialog.querySelector<HTMLElement>(selector);
   assertTrue(!!button);
-  // Should close the dialog.
+  // Should close the dialog and trigger 'passwordsPrivate.resetImporter'.
   button.click();
-  flush();
+  const deleteFile = await passwordManager.whenCalled('resetImporter');
+  assertEquals(shouldDeleteFile, deleteFile);
+  await flushTasks();
   assertFalse(!!importer.shadowRoot!.querySelector<CrDialogElement>('#dialog'));
 }
 
 async function assertErrorStateAndClose(
-    importer: PasswordsImporterElement, expectedDescription: string) {
+    importer: PasswordsImporterElement,
+    passwordManager: TestPasswordManagerProxy, expectedDescription: string) {
   const dialog = importer.shadowRoot!.querySelector<CrDialogElement>('#dialog');
   assertTrue(!!dialog);
   assertTrue(dialog.open);
@@ -86,7 +91,7 @@
       dialog, '#selectFileButton', importer.i18n('selectFile'));
   assertVisibleTextContent(dialog, '#closeButton', importer.i18n('close'));
 
-  assertButtonShouldCloseDialog(importer, dialog, '#closeButton');
+  await closeDialogHelper(importer, passwordManager, dialog, '#closeButton');
 }
 
 suite('PasswordsImporterTest', function() {
@@ -140,7 +145,7 @@
     await triggerImportHelper(importer, passwordManager);
   });
 
-  test('store picker dialog has correct state', function() {
+  test('store picker dialog has correct state', async function() {
     const importer = createPasswordsImporter(
         /*isUserSyncingPasswords=*/ false, /*isAccountStoreUser=*/ true,
         /*accountEmail=*/ 'test@test.com');
@@ -164,7 +169,7 @@
         dialog, '#selectFileButton', importer.i18n('selectFile'));
     assertVisibleTextContent(dialog, '#cancelButton', importer.i18n('cancel'));
 
-    assertButtonShouldCloseDialog(importer, dialog, '#cancelButton');
+    await closeDialogHelper(importer, passwordManager, dialog, '#cancelButton');
   });
 
   test('account store user can import passwords to device', async function() {
@@ -199,7 +204,8 @@
     await triggerImportHelper(importer, passwordManager, expectedStore);
   });
 
-  test('has correct success state with no errors', async function() {
+  test('M1: has correct success state with no errors', async function() {
+    loadTimeData.overrideValues({enablePasswordsImportM2: false});
     const importer = createPasswordsImporter();
     passwordManager.setImportResults({
       status: chrome.passwordsPrivate.ImportResultsStatus.SUCCESS,
@@ -221,6 +227,9 @@
         dialog, '#title', importer.i18n('importPasswordsSuccessTitle'));
 
     assertTrue(isChildVisible(dialog, '#tipBox', /*checkLightDom=*/ true));
+
+    assertFalse(
+        isChildVisible(dialog, '#deleteFileOption', /*checkLightDom=*/ true));
     assertFalse(
         isChildVisible(dialog, '#failuresSummary', /*checkLightDom=*/ true));
 
@@ -236,9 +245,117 @@
 
     assertVisibleTextContent(dialog, '#closeButton', importer.i18n('close'));
 
-    assertButtonShouldCloseDialog(importer, dialog, '#closeButton');
+    await closeDialogHelper(importer, passwordManager, dialog, '#closeButton');
   });
 
+  test('M2: has correct success state with no errors', async function() {
+    loadTimeData.overrideValues({enablePasswordsImportM2: true});
+    const importer = createPasswordsImporter();
+    passwordManager.setImportResults({
+      status: chrome.passwordsPrivate.ImportResultsStatus.SUCCESS,
+      numberImported: 42,
+      displayedEntries: [],
+      fileName: 'test.csv',
+    });
+
+    await triggerImportHelper(importer, passwordManager);
+    await pluralString.whenCalled('getPluralString');
+    await flushTasks();
+
+    const dialog =
+        importer.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+    assertTrue(!!dialog);
+    assertTrue(dialog.open);
+
+    assertVisibleTextContent(
+        dialog, '#title', importer.i18n('importPasswordsSuccessTitle'));
+
+    assertTrue(
+        isChildVisible(dialog, '#deleteFileOption', /*checkLightDom=*/ true));
+
+    assertFalse(isChildVisible(dialog, '#tipBox', /*checkLightDom=*/ true));
+    assertFalse(
+        isChildVisible(dialog, '#failuresSummary', /*checkLightDom=*/ true));
+
+    const deleteFileOption = dialog.querySelector('#deleteFileOption');
+    assertTrue(!!deleteFileOption);
+    assertEquals(
+        deleteFileOption.innerHTML.toString(),
+        importer
+            .i18nAdvanced(
+                'importPasswordsDeleteFileOption',
+                {attrs: ['class'], substitutions: ['test.csv']})
+            .toString());
+
+    assertVisibleTextContent(dialog, '#closeButton', importer.i18n('close'));
+
+    await closeDialogHelper(importer, passwordManager, dialog, '#closeButton');
+  });
+
+  test(
+      'M2: close button triggers file deletion with ticked checkbox',
+      async function() {
+        loadTimeData.overrideValues({enablePasswordsImportM2: true});
+        const importer = createPasswordsImporter();
+        passwordManager.setImportResults({
+          status: chrome.passwordsPrivate.ImportResultsStatus.SUCCESS,
+          numberImported: 42,
+          displayedEntries: [],
+          fileName: 'test.csv',
+        });
+
+        await triggerImportHelper(importer, passwordManager);
+        await pluralString.whenCalled('getPluralString');
+        await flushTasks();
+
+        const dialog =
+            importer.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+        assertTrue(!!dialog);
+        assertTrue(dialog.open);
+
+        const deleteFileOption =
+            dialog.querySelector<HTMLElement>('#deleteFileOption');
+        assertTrue(!!deleteFileOption);
+        deleteFileOption.click();
+        flush();
+
+        await closeDialogHelper(
+            importer, passwordManager, dialog, '#closeButton',
+            /*shouldDeleteFile=*/ true);
+      });
+
+  test(
+      'M2: view passwords triggers file deletion with ticked checkbox',
+      async function() {
+        loadTimeData.overrideValues({enablePasswordsImportM2: true});
+        const importer = createPasswordsImporter();
+        passwordManager.setImportResults({
+          status: chrome.passwordsPrivate.ImportResultsStatus.SUCCESS,
+          numberImported: 42,
+          displayedEntries: [],
+          fileName: 'test.csv',
+        });
+
+        await triggerImportHelper(importer, passwordManager);
+        await pluralString.whenCalled('getPluralString');
+        await flushTasks();
+
+        const dialog =
+            importer.shadowRoot!.querySelector<CrDialogElement>('#dialog');
+        assertTrue(!!dialog);
+        assertTrue(dialog.open);
+
+        const deleteFileOption =
+            dialog.querySelector<HTMLElement>('#deleteFileOption');
+        assertTrue(!!deleteFileOption);
+        deleteFileOption.click();
+        flush();
+
+        await closeDialogHelper(
+            importer, passwordManager, dialog, '#viewPasswordsButton',
+            /*shouldDeleteFile=*/ true);
+      });
+
 
   test('view passwords navigates to the passwords page', async function() {
     const importer = createPasswordsImporter();
@@ -260,14 +377,9 @@
 
     assertVisibleTextContent(
         dialog, '#viewPasswordsButton', importer.i18n('viewPasswordsButton'));
-    const button = dialog.querySelector<HTMLElement>('#viewPasswordsButton');
-    assertTrue(!!button);
     // Should close the dialog and navigate to PASSWORDS page.
-    button.click();
-    flush();
-    assertFalse(
-        !!importer.shadowRoot!.querySelector<CrDialogElement>('#dialog'));
-    await flushTasks();
+    await closeDialogHelper(
+        importer, passwordManager, dialog, '#viewPasswordsButton');
     assertEquals(Page.PASSWORDS, Router.getInstance().currentRoute.page);
   });
 
@@ -365,13 +477,15 @@
 
     // Success tip should not be visible.
     assertFalse(isChildVisible(dialog, '#tipBox', /*checkLightDom=*/ true));
+    assertFalse(
+        isChildVisible(dialog, '#deleteFileOption', /*checkLightDom=*/ true));
 
     assertTrue(
         isChildVisible(dialog, '#failuresSummary', /*checkLightDom=*/ true));
 
     assertVisibleTextContent(dialog, '#closeButton', importer.i18n('close'));
 
-    assertButtonShouldCloseDialog(importer, dialog, '#closeButton');
+    await closeDialogHelper(importer, passwordManager, dialog, '#closeButton');
   });
 
   test('bad format error dialog is correct', async function() {
@@ -385,7 +499,7 @@
     await triggerImportHelper(importer, passwordManager);
 
     await assertErrorStateAndClose(
-        importer,
+        importer, passwordManager,
         importer
             .i18nAdvanced(
                 'importPasswordsBadFormatError',
@@ -411,7 +525,7 @@
     await triggerImportHelper(importer, passwordManager);
 
     await assertErrorStateAndClose(
-        importer,
+        importer, passwordManager,
         importer.i18nAdvanced('importPasswordsUnknownError').toString());
   });
 
@@ -427,7 +541,7 @@
     await triggerImportHelper(importer, passwordManager);
 
     await assertErrorStateAndClose(
-        importer,
+        importer, passwordManager,
         importer.i18nAdvanced('importPasswordsLimitExceeded').toString());
   });
 
@@ -442,7 +556,7 @@
     await triggerImportHelper(importer, passwordManager);
 
     await assertErrorStateAndClose(
-        importer,
+        importer, passwordManager,
         importer.i18nAdvanced('importPasswordsFileSizeExceeded').toString());
   });
 
@@ -468,6 +582,6 @@
         dialog, '#description', importer.i18n('importPasswordsAlreadyActive'));
     assertVisibleTextContent(dialog, '#closeButton', importer.i18n('close'));
 
-    assertButtonShouldCloseDialog(importer, dialog, '#closeButton');
+    await closeDialogHelper(importer, passwordManager, dialog, '#closeButton');
   });
 });
diff --git a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
index ae8b7530..618ec34 100644
--- a/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
+++ b/chrome/test/data/webui/password_manager/test_password_manager_proxy.ts
@@ -71,6 +71,7 @@
       'recordPasswordViewInteraction',
       'removeBlockedSite',
       'removeSavedPassword',
+      'resetImporter',
       'requestCredentialsDetails',
       'requestExportProgressStatus',
       'requestPlaintextPassword',
@@ -328,6 +329,11 @@
     return Promise.resolve(this.importResults_);
   }
 
+  resetImporter(deleteFile: boolean) {
+    this.methodCalled('resetImporter', deleteFile);
+    return Promise.resolve();
+  }
+
   isOptedInForAccountStorage() {
     this.methodCalled('isOptedInForAccountStorage');
     return Promise.resolve(this.data.isOptedInAccountStorage);
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 306d3c3..01f51a2 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -96,6 +96,7 @@
     "os_settings_page_test.js",
     "os_settings_search_box_test.js",
     "os_sync_controls_subpage_test.js",
+    "passpoint_subpage_test.js",
     "people_page_account_manager_subpage_test.js",
     "personalization_page_with_personalization_hub_test.js",
     "privacy_hub_subpage_tests.js",
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page_tests.js b/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
index 19f5ac4c..2e497b0 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/internet_page_tests.js
@@ -882,10 +882,20 @@
       });
 
   test('Nagivate to Passpoint detail page', async () => {
+    const subId = 'a_passpoint_id';
+    const sub = {
+      id: subId,
+      domains: ['passpoint.example.com'],
+      friendlyName: 'Passpoint Example Ltd.',
+      provisioningSource: 'app.passpoint.example.com',
+      trustedCa: '',
+      expirationEpochMs: 0n,
+    };
+    passpointService_.addSubscription(sub);
     await init();
 
     const params = new URLSearchParams();
-    params.append('id', 'a_passpoint_id');
+    params.append('id', subId);
 
     // Navigate straight to Passpoint detail subpage.
     Router.getInstance().navigateTo(routes.PASSPOINT_DETAIL, params);
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index 40efa91..6ae1da4 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -528,6 +528,14 @@
    'ParentalControlsPage',
    'parental_controls_page/parental_controls_page_test.js'
  ],
+ [
+   'PasspointSubpage', 'passpoint_subpage_test.js', {
+     enabled: [
+       'ash::features::kPasspointARCSupport',
+       'ash::features::kPasspointSettings',
+     ]
+   }
+ ],
  ['PeoplePage', 'os_people_page_test.js'],
  [
    'PeoplePageAccountManagerSubpage',
diff --git a/chrome/test/data/webui/settings/chromeos/passpoint_subpage_test.js b/chrome/test/data/webui/settings/chromeos/passpoint_subpage_test.js
new file mode 100644
index 0000000..29dd04a
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/passpoint_subpage_test.js
@@ -0,0 +1,273 @@
+// 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 'chrome://os-settings/chromeos/lazy_load.js';
+
+import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {assert} from 'chrome://resources/ash/common/assert.js';
+import {MojoConnectivityProvider} from 'chrome://resources/ash/common/connectivity/mojo_connectivity_provider.js';
+import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
+import {AppType} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
+import {FakePasspointService} from 'chrome://webui-test/chromeos/fake_passpoint_service_mojom.js';
+import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+
+import {setupFakeHandler} from './app_management/test_util.js';
+
+suite('PasspointSubpage', () => {
+  /** @type {?FakeNetworkConfig} */
+  let networkConfigApi_ = null;
+  /** @type {?FakePasspointService} */
+  let passpointServiceApi_ = null;
+  /** @type {?FakePageHandler} */
+  let fakeHandler = null;
+  /** @type {?SettingPasspointSubpageElement} */
+  let passpointSubpage_ = null;
+
+  const kCaHash = 'CAHASH';
+  const kCaPem = 'test-pem';
+  const kCaCN = 'Passpoint Example Certificate Authority';
+
+  /**
+   * @return {!Promise<unknown>}
+   * @private
+   */
+  function flushAsync() {
+    flush();
+    // Use setTimeout to wait for the next macrotask.
+    return new Promise(resolve => setTimeout(resolve));
+  }
+
+  /**
+   * @param {!PasspointSubscription} sub
+   * @return {!Promise<unknown>}
+   */
+  async function init(sub) {
+    const serverCas = [];
+    serverCas.push({
+      hash: kCaHash,
+      pemOrId: kCaPem,
+      issuedTo: kCaCN,
+    });
+    networkConfigApi_.setCertificatesForTest(serverCas, []);
+    passpointServiceApi_.addSubscription(sub);
+    await waitAfterNextRender(passpointSubpage_);
+
+    const params = new URLSearchParams();
+    params.append('id', sub.id);
+    Router.getInstance().navigateTo(routes.PASSPOINT_DETAIL, params);
+    return flushAsync();
+  }
+
+  function getDomainsListItems() {
+    const domains =
+        passpointSubpage_.shadowRoot.querySelector('#passpointDomainsList');
+    assertTrue(!!domains);
+    return domains.querySelectorAll('div.list-item');
+  }
+
+  function getExpirationDateItem() {
+    return passpointSubpage_.shadowRoot.querySelector(
+        '#passpointExpirationDate');
+  }
+
+  function getSourceText() {
+    const div =
+        passpointSubpage_.shadowRoot.querySelector('#passpointSourceText');
+    assertTrue(!!div);
+    return div.textContent.trim();
+  }
+
+  function getCertificateName() {
+    const div =
+        passpointSubpage_.shadowRoot.querySelector('#passpointCertificateName');
+    assertTrue(!!div);
+    return div.textContent.trim();
+  }
+
+  function getElement(id) {
+    const element = passpointSubpage_.shadowRoot.querySelector(`#${id}`);
+    assertTrue(!!element);
+    return element;
+  }
+
+  function getRemovalDialog() {
+    return passpointSubpage_.shadowRoot.querySelector('#removalDialog');
+  }
+
+  suiteSetup(() => {
+    loadTimeData.overrideValues({
+      isPasspointEnabled: true,
+      isPasspointSettingsEnabled: true,
+    });
+    networkConfigApi_ = new FakeNetworkConfig();
+    MojoInterfaceProviderImpl.getInstance().remote_ = networkConfigApi_;
+    passpointServiceApi_ = new FakePasspointService();
+    MojoConnectivityProvider.getInstance().setPasspointServiceForTest(
+        passpointServiceApi_);
+    fakeHandler = setupFakeHandler();
+
+    // Disable animations so sub-pages open within one event loop.
+    testing.Test.disableAnimationsAndTransitions();
+  });
+
+  setup(async () => {
+    PolymerTest.clearBody();
+    passpointSubpage_ = document.createElement('settings-passpoint-subpage');
+    assert(passpointSubpage_);
+
+    networkConfigApi_.resetForTest();
+    passpointServiceApi_.resetForTest();
+
+    document.body.appendChild(passpointSubpage_);
+    await waitAfterNextRender(passpointSubpage_);
+  });
+
+  teardown(function() {
+    passpointSubpage_.remove();
+    passpointSubpage_ = null;
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  test('Show Passpoint subscription', async () => {
+    const sub = {
+      id: 'sub-id',
+      domains: ['passpoint.example.com'],
+      friendlyName: 'Passpoint Example Ltd.',
+      provisioningSource: 'app.passpoint.example.com',
+      trustedCa: kCaPem,
+      expirationEpochMs: 0n,
+    };
+    await init(sub);
+
+    // No app registered, package name should be displayed.
+    assertEquals(sub.provisioningSource, getSourceText());
+    // Only one domain in the list.
+    const list = getDomainsListItems();
+    assertEquals(1, list.length);
+    // No expiration time.
+    const item = getExpirationDateItem();
+    assertFalse(!!item);
+    // Certificate has a common name.
+    assertEquals(kCaCN, getCertificateName());
+  });
+
+  test(
+      'Show Passpoint subscription with domains and expiration time',
+      async () => {
+        // Create a date 7 days ahead of now.
+        const date = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
+
+        const sub = {
+          id: 'sub-id',
+          domains: ['passpoint.example.com', 'passpoint2.example.com'],
+          friendlyName: 'Passpoint Example Ltd.',
+          provisioningSource: 'app.passpoint.example.com',
+          trustedCa: kCaPem,
+          expirationEpochMs: BigInt(date.getTime()),
+        };
+        await init(sub);
+
+        // No app registered, package name should be displayed.
+        assertEquals(sub.provisioningSource, getSourceText());
+        // Only one domain in the list.
+        const list = getDomainsListItems();
+        assertEquals(2, list.length);
+        // Expiration time is displayed.
+        const item = getExpirationDateItem();
+        assertTrue(!!item);
+        // Certificate has a common name.
+        assertEquals(kCaCN, getCertificateName());
+      });
+
+  test('Show Passpoint subscription without certificate', async () => {
+    const sub = {
+      id: 'sub-id',
+      domains: ['passpoint.example.com'],
+      friendlyName: 'Passpoint Example Ltd.',
+      provisioningSource: 'app.passpoint.example.com',
+      expirationEpochMs: 0n,
+    };
+    await init(sub);
+
+    // Certificate has a common name.
+    assertEquals(
+        passpointSubpage_.i18n('passpointSystemCALabel'), getCertificateName());
+  });
+
+  test('Show Passpoint subscription with app', async () => {
+    const sub = {
+      id: 'sub-id',
+      domains: ['passpoint.example.com'],
+      friendlyName: 'Passpoint Example Ltd.',
+      provisioningSource: 'app.passpoint.example.com',
+      trustedCa: kCaPem,
+      expirationEpochMs: 0n,
+    };
+    const appTitle = 'My Passpoint App';
+    const app = {
+      type: AppType.kArc,
+      title: appTitle,
+      publisherId: sub.provisioningSource,
+    };
+    fakeHandler.setApps([app]);
+    await init(sub);
+
+    // Only one domain in the list.
+    const list = getDomainsListItems();
+    assertEquals(1, list.length);
+    // No expiration time.
+    const item = getExpirationDateItem();
+    assertFalse(!!item);
+    // App name is displayed as subscription source.
+    assertEquals(appTitle, getSourceText());
+    // Certificate has a common name.
+    assertEquals(kCaCN, getCertificateName());
+  });
+
+  test('Subscription removal', async () => {
+    const subId = 'sub-id';
+    const sub = {
+      id: subId,
+      domains: ['passpoint.example.com'],
+      friendlyName: 'Passpoint Example Ltd.',
+      provisioningSource: 'app.passpoint.example.com',
+      expirationEpochMs: 0n,
+    };
+    await init(sub);
+
+    // Trigger a remove.
+    const removeButton = getElement('removeButton');
+    removeButton.click();
+    await waitAfterNextRender(removeButton);
+    let dialog = getRemovalDialog();
+    assertTrue(!!dialog);
+
+    // Cancel the dialog.
+    const cancelButton = getElement('removalCancelButton');
+    cancelButton.click();
+    await waitAfterNextRender(cancelButton);
+    dialog = getRemovalDialog();
+    assertFalse(!!dialog);
+
+    // Trigger a remove again.
+    removeButton.click();
+    await waitAfterNextRender(removeButton);
+    dialog = getRemovalDialog();
+    assertTrue(!!dialog);
+
+    // Confirm the removal.
+    const confirmButton = getElement('removalConfirmButton');
+    confirmButton.click();
+    await waitAfterNextRender(confirmButton);
+
+    // Check the subscription is removed.
+    dialog = getRemovalDialog();
+    assertFalse(!!dialog);
+    const resp = await passpointServiceApi_.getPasspointSubscription(subId);
+    assertEquals(null, resp.result);
+  });
+});
diff --git a/components/android_autofill/browser/android_autofill_manager.cc b/components/android_autofill/browser/android_autofill_manager.cc
index b9d26e9..60b056e 100644
--- a/components/android_autofill/browser/android_autofill_manager.cc
+++ b/components/android_autofill/browser/android_autofill_manager.cc
@@ -69,6 +69,7 @@
     mojom::SubmissionSource source) {
   address_logger_->OnWillSubmitForm();
   payments_logger_->OnWillSubmitForm();
+  password_logger_->OnWillSubmitForm();
   if (auto* provider = GetAutofillProvider())
     provider->OnFormSubmitted(this, form, known_success, source);
 }
@@ -246,6 +247,8 @@
   address_logger_ = std::make_unique<FormEventLoggerWeblayerAndroid>("Address");
   payments_logger_ =
       std::make_unique<FormEventLoggerWeblayerAndroid>("CreditCard");
+  password_logger_ =
+      std::make_unique<FormEventLoggerWeblayerAndroid>("Password");
 }
 
 FormEventLoggerWeblayerAndroid* AndroidAutofillManager::GetEventFormLogger(
@@ -267,6 +270,7 @@
     case FormType::kCreditCardForm:
       return payments_logger_.get();
     case FormType::kPasswordForm:
+      return password_logger_.get();
     case FormType::kUnknownFormType:
       return nullptr;
   }
diff --git a/components/android_autofill/browser/android_autofill_manager.h b/components/android_autofill/browser/android_autofill_manager.h
index ebe7951..179b2b4 100644
--- a/components/android_autofill/browser/android_autofill_manager.h
+++ b/components/android_autofill/browser/android_autofill_manager.h
@@ -171,6 +171,7 @@
   raw_ptr<AutofillProvider> autofill_provider_for_testing_ = nullptr;
   std::unique_ptr<FormEventLoggerWeblayerAndroid> address_logger_;
   std::unique_ptr<FormEventLoggerWeblayerAndroid> payments_logger_;
+  std::unique_ptr<FormEventLoggerWeblayerAndroid> password_logger_;
 
   base::WeakPtrFactory<AndroidAutofillManager> weak_ptr_factory_{this};
 };
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index a663c484..1b465af 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -1070,14 +1070,6 @@
     SendFocusedInputChangedNotificationToBrowser(focused_element);
   }
 
-  // PasswordGenerationAgent needs to know about focus changes, even if there is
-  // no focused element.
-  if (password_generation_agent_ &&
-      password_generation_agent_->FocusedNodeHasChanged(focused_element)) {
-    is_generation_popup_possibly_visible_ = true;
-    is_popup_possibly_visible_ = true;
-  }
-
   if (!IsKeyboardAccessoryEnabled() && focus_requires_scroll_)
     HandleFocusChangeComplete();
 
@@ -1219,6 +1211,12 @@
 
   focused_node_was_last_clicked_ = false;
 
+  if (password_generation_agent_ &&
+      password_generation_agent_->HandleFocusChangeComplete(focused_element)) {
+    is_generation_popup_possibly_visible_ = true;
+    is_popup_possibly_visible_ = true;
+  }
+
   SendPotentiallySubmittedFormToBrowser();
 }
 
diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc
index f9b2bedf..adeea7e 100644
--- a/components/autofill/content/renderer/password_generation_agent.cc
+++ b/components/autofill/content/renderer/password_generation_agent.cc
@@ -30,6 +30,7 @@
 #include "third_party/blink/public/platform/web_security_origin.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_form_control_element.h"
 #include "third_party/blink/public/web/web_form_element.h"
 #include "third_party/blink/public/web/web_input_element.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -483,7 +484,7 @@
   return true;
 }
 
-bool PasswordGenerationAgent::FocusedNodeHasChanged(
+bool PasswordGenerationAgent::HandleFocusChangeComplete(
     const blink::WebNode& node) {
   if (node.IsNull() || !node.IsElementNode()) {
     return false;
@@ -619,8 +620,9 @@
     // Notify `password_agent_` of text changes to the other confirmation
     // password fields.
     for (const auto& password_element :
-         current_generation_item_->password_elements_)
+         current_generation_item_->password_elements_) {
       password_agent_->UpdateStateForTextChange(password_element);
+    }
   }
   return true;
 }
@@ -691,8 +693,10 @@
   // Do not treat the password as generated, either here or in the browser.
   current_generation_item_->password_is_generated_ = false;
   current_generation_item_->password_edited_ = false;
-  for (WebInputElement& password : current_generation_item_->password_elements_)
+  for (WebInputElement& password :
+       current_generation_item_->password_elements_) {
     password.SetAutofillState(WebAutofillState::kNotFilled);
+  }
   password_generation::LogPasswordGenerationEvent(
       password_generation::PASSWORD_DELETED);
   // Clear all other password fields.
diff --git a/components/autofill/content/renderer/password_generation_agent.h b/components/autofill/content/renderer/password_generation_agent.h
index 2a1aafe..826a11e 100644
--- a/components/autofill/content/renderer/password_generation_agent.h
+++ b/components/autofill/content/renderer/password_generation_agent.h
@@ -69,7 +69,7 @@
   bool TextDidChangeInTextField(const blink::WebInputElement& element);
 
   // Returns true if the newly focused node caused the generation UI to show.
-  bool FocusedNodeHasChanged(const blink::WebNode& node);
+  bool HandleFocusChangeComplete(const blink::WebNode& node);
 
   // Event forwarded by AutofillAgent from WebAutofillClient, informing that
   // the text field editing has ended, which means that the field is not
diff --git a/components/autofill/core/browser/form_data_importer_utils.cc b/components/autofill/core/browser/form_data_importer_utils.cc
index db016f3..b7ff614 100644
--- a/components/autofill/core/browser/form_data_importer_utils.cc
+++ b/components/autofill/core/browser/form_data_importer_utils.cc
@@ -109,13 +109,12 @@
       !(is_line1_missing || is_city_missing || is_state_missing ||
         is_zip_missing || is_zip_or_state_requirement_violated ||
         is_line1_or_house_number_violated);
-  if (is_minimum_address &&
-      base::FeatureList::IsEnabled(
-          features::kAutofillRequireNameForProfileImport)) {
-    is_minimum_address &= ValidateAndLog(
-        /*required=*/true, {NAME_FULL},
-        AddressImportRequirement::kNameRequirementFulfilled,
-        AddressImportRequirement::kNameRequirementViolated);
+  // TODO(crbug.com/1413205): Merge this into is_minimum_address.
+  if (is_minimum_address && country.requires_full_name()) {
+    is_minimum_address &=
+        ValidateAndLog(/*required=*/true, {NAME_FULL},
+                       AddressImportRequirement::kNameRequirementFulfilled,
+                       AddressImportRequirement::kNameRequirementViolated);
   }
   if (collect_metrics) {
     autofill_metrics::
diff --git a/components/autofill/core/browser/form_types.cc b/components/autofill/core/browser/form_types.cc
index 71cdf9ef..f1a401f 100644
--- a/components/autofill/core/browser/form_types.cc
+++ b/components/autofill/core/browser/form_types.cc
@@ -51,18 +51,16 @@
   return "UnknownFormType";
 }
 
-bool FormHasAllEmtyCreditCardFields(const FormStructure& form_structure) {
+bool FormHasAllCreditCardFields(const FormStructure& form_structure) {
   bool has_card_number_field = base::ranges::any_of(
       form_structure, [](const std::unique_ptr<AutofillField>& autofill_field) {
         return autofill_field->Type().GetStorableType() ==
-                   ServerFieldType::CREDIT_CARD_NUMBER &&
-               SanitizedFieldIsEmpty(autofill_field->value);
+               ServerFieldType::CREDIT_CARD_NUMBER;
       });
 
   bool has_expiration_date_field = base::ranges::any_of(
       form_structure, [](const std::unique_ptr<AutofillField>& autofill_field) {
-        return autofill_field->HasExpirationDateType() &&
-               SanitizedFieldIsEmpty(autofill_field->value);
+        return autofill_field->HasExpirationDateType();
       });
 
   return has_card_number_field && has_expiration_date_field;
diff --git a/components/autofill/core/browser/form_types.h b/components/autofill/core/browser/form_types.h
index 1fc41cb..379025d 100644
--- a/components/autofill/core/browser/form_types.h
+++ b/components/autofill/core/browser/form_types.h
@@ -22,7 +22,7 @@
 
 // Returns true if the form contains fields that represent the card number and
 // the card expiration date.
-bool FormHasAllEmtyCreditCardFields(const FormStructure& form_structure);
+bool FormHasAllCreditCardFields(const FormStructure& form_structure);
 
 FormType FieldTypeGroupToFormType(FieldTypeGroup field_type_group);
 
diff --git a/components/autofill/core/browser/form_types_unittest.cc b/components/autofill/core/browser/form_types_unittest.cc
index 69827d3..0c9ba63 100644
--- a/components/autofill/core/browser/form_types_unittest.cc
+++ b/components/autofill/core/browser/form_types_unittest.cc
@@ -43,22 +43,18 @@
   FormStructure form_structure(form);
   FormStructureTestApi(&form_structure).SetFieldTypes(test_case.field_types);
 
-  EXPECT_THAT(FormHasAllEmtyCreditCardFields(form_structure),
+  EXPECT_THAT(FormHasAllCreditCardFields(form_structure),
               testing::Eq(test_case.expected_result));
 }
 
 INSTANTIATE_TEST_SUITE_P(
     All,
     FormTypesTest,
-    testing::Values(
-        FormTypesTestCase{{CREDIT_CARD_NUMBER, CREDIT_CARD_EXP_MONTH,
-                           CREDIT_CARD_EXP_2_DIGIT_YEAR},
-                          {u"", u"", u""},
-                          true},
-        FormTypesTestCase{{CREDIT_CARD_NUMBER}, {u""}, false},
-        FormTypesTestCase{{CREDIT_CARD_NUMBER, CREDIT_CARD_EXP_MONTH,
-                           CREDIT_CARD_EXP_2_DIGIT_YEAR},
-                          {u"411111111111", u"", u""},
-                          false}));
+    testing::Values(FormTypesTestCase{{CREDIT_CARD_NUMBER,
+                                       CREDIT_CARD_EXP_MONTH,
+                                       CREDIT_CARD_EXP_2_DIGIT_YEAR},
+                                      {u"", u"", u""},
+                                      true},
+                    FormTypesTestCase{{CREDIT_CARD_NUMBER}, {u""}, false}));
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/geo/autofill_country.cc b/components/autofill/core/browser/geo/autofill_country.cc
index 44fd060..b8bbfe01 100644
--- a/components/autofill/core/browser/geo/autofill_country.cc
+++ b/components/autofill/core/browser/geo/autofill_country.cc
@@ -10,12 +10,11 @@
 #include "base/containers/contains.h"
 #include "base/containers/fixed_flat_map.h"
 #include "base/containers/fixed_flat_set.h"
-#include "base/feature_list.h"
+
 #include "base/strings/string_piece_forward.h"
 #include "base/strings/string_util.h"
 #include "components/autofill/core/browser/geo/country_data.h"
 #include "components/autofill/core/browser/geo/country_names.h"
-#include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_internals/log_message.h"
 #include "components/autofill/core/common/logging/log_buffer.h"
 #include "third_party/icu/source/common/unicode/locid.h"
@@ -188,6 +187,9 @@
 }
 
 bool AutofillCountry::IsAddressFieldRequired(AddressField address_field) const {
+  if (address_field == AddressField::RECIPIENT && requires_full_name()) {
+    return true;
+  }
   auto* mapping_it = kRequiredFieldMapping.find(address_field);
   return mapping_it != kRequiredFieldMapping.end() &&
          (required_fields_for_address_import_ & mapping_it->second);
diff --git a/components/autofill/core/browser/geo/autofill_country.h b/components/autofill/core/browser/geo/autofill_country.h
index be2fa63..cb4f6835 100644
--- a/components/autofill/core/browser/geo/autofill_country.h
+++ b/components/autofill/core/browser/geo/autofill_country.h
@@ -8,8 +8,10 @@
 #include <string>
 
 #include "base/containers/span.h"
+#include "base/feature_list.h"
 #include "base/strings/string_piece.h"
 #include "components/autofill/core/browser/geo/country_data.h"
+#include "components/autofill/core/common/autofill_features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/libaddressinput/src/cpp/include/libaddressinput/address_field.h"
 
@@ -79,6 +81,12 @@
   // the constructor. If no `locale` was provided, an empty string is returned.
   const std::u16string& name() const { return name_; }
 
+  // Full name is expected in a complete address for this country.
+  bool requires_full_name() const {
+    return base::FeatureList::IsEnabled(
+        features::kAutofillRequireNameForProfileImport);
+  }
+
   // City is expected in a complete address for this country.
   bool requires_city() const {
     return (required_fields_for_address_import_ & ADDRESS_REQUIRES_CITY) != 0;
diff --git a/components/autofill/core/browser/geo/autofill_country_unittest.cc b/components/autofill/core/browser/geo/autofill_country_unittest.cc
index 0c3e2947..e8050a41 100644
--- a/components/autofill/core/browser/geo/autofill_country_unittest.cc
+++ b/components/autofill/core/browser/geo/autofill_country_unittest.cc
@@ -142,6 +142,26 @@
   EXPECT_TRUE(country.IsAddressFieldRequired(AddressField::STREET_ADDRESS));
 }
 
+// Test the full name requirement depending on the
+// kAutofillRequireNameForProfileImport feature flag.
+TEST(AutofillCountryTest, IsAddressFieldRequired_RequireName) {
+  AutofillCountry country("US", "en_US");
+
+  {
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndDisableFeature(
+        features::kAutofillRequireNameForProfileImport);
+    EXPECT_FALSE(country.IsAddressFieldRequired(AddressField::RECIPIENT));
+  }
+
+  {
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndEnableFeature(
+        features::kAutofillRequireNameForProfileImport);
+    EXPECT_TRUE(country.IsAddressFieldRequired(AddressField::RECIPIENT));
+  }
+}
+
 // Test mapping all country codes to country names.
 TEST(AutofillCountryTest, AllCountryCodesHaveCountryName) {
   std::set<std::string> expected_failures;
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index b79f696..14e1fa51 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "20.45",
-  "log_list_timestamp": "2023-04-20T12:54:44Z",
+  "version": "20.46",
+  "log_list_timestamp": "2023-04-21T12:54:37Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/dom_distiller/content/renderer/distillability_agent.cc b/components/dom_distiller/content/renderer/distillability_agent.cc
index aa4cf60..abea0d0 100644
--- a/components/dom_distiller/content/renderer/distillability_agent.cc
+++ b/components/dom_distiller/content/renderer/distillability_agent.cc
@@ -27,15 +27,6 @@
 const char* const kFilterlist[] = {"www.reddit.com", "tools.usps.com",
                                    "old.reddit.com"};
 
-enum RejectionBuckets {
-  NOT_ARTICLE = 0,
-  MOBILE_FRIENDLY,
-  FILTERED,
-  TOO_SHORT,
-  NOT_REJECTED,
-  REJECTION_BUCKET_BOUNDARY
-};
-
 // Returns whether it is necessary to send updates back to the browser.
 // The number of updates can be from 0 to 2. See the tests in
 // "distillable_page_utils_browsertest.cc".
@@ -150,56 +141,6 @@
                        long_score, long_article, filtered);
   }
 
-  if (!features.is_mobile_friendly) {
-    int score_int = std::round(score * 100);
-    if (score > 0) {
-      UMA_HISTOGRAM_COUNTS_1000("DomDistiller.DistillabilityScoreNMF.Positive",
-                                score_int);
-    } else {
-      UMA_HISTOGRAM_COUNTS_1000("DomDistiller.DistillabilityScoreNMF.Negative",
-                                -score_int);
-    }
-    if (distillable) {
-      // The long-article model is trained with pages that are
-      // non-mobile-friendly, and distillable (deemed by the first model), so
-      // only record on that type of pages.
-      int long_score_int = std::round(long_score * 100);
-      if (long_score > 0) {
-        UMA_HISTOGRAM_COUNTS_1000("DomDistiller.LongArticleScoreNMF.Positive",
-                                  long_score_int);
-      } else {
-        UMA_HISTOGRAM_COUNTS_1000("DomDistiller.LongArticleScoreNMF.Negative",
-                                  -long_score_int);
-      }
-    }
-  }
-
-  int bucket = static_cast<unsigned>(features.is_mobile_friendly) |
-               (static_cast<unsigned>(distillable) << 1);
-  if (is_last) {
-    UMA_HISTOGRAM_ENUMERATION("DomDistiller.PageDistillableAfterLoading",
-                              bucket, 4);
-  } else {
-    UMA_HISTOGRAM_ENUMERATION("DomDistiller.PageDistillableAfterParsing",
-                              bucket, 4);
-    if (!distillable) {
-      UMA_HISTOGRAM_ENUMERATION("DomDistiller.DistillabilityRejection",
-                                NOT_ARTICLE, REJECTION_BUCKET_BOUNDARY);
-    } else if (features.is_mobile_friendly) {
-      UMA_HISTOGRAM_ENUMERATION("DomDistiller.DistillabilityRejection",
-                                MOBILE_FRIENDLY, REJECTION_BUCKET_BOUNDARY);
-    } else if (filtered) {
-      UMA_HISTOGRAM_ENUMERATION("DomDistiller.DistillabilityRejection",
-                                FILTERED, REJECTION_BUCKET_BOUNDARY);
-    } else if (!long_article) {
-      UMA_HISTOGRAM_ENUMERATION("DomDistiller.DistillabilityRejection",
-                                TOO_SHORT, REJECTION_BUCKET_BOUNDARY);
-    } else {
-      UMA_HISTOGRAM_ENUMERATION("DomDistiller.DistillabilityRejection",
-                                NOT_REJECTED, REJECTION_BUCKET_BOUNDARY);
-    }
-  }
-
   if (filtered) {
     return false;
   }
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index 04c7ea1d..c4362f78 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -591,6 +591,7 @@
   if (pending_state_.clip_rect == clip_rect) {
     return;
   }
+  has_pending_contents_ = true;
   pending_state_.clip_rect = clip_rect;
 }
 
diff --git a/components/history/core/browser/history_backend_db_unittest.cc b/components/history/core/browser/history_backend_db_unittest.cc
index 4d72cf8..e3fd593 100644
--- a/components/history/core/browser/history_backend_db_unittest.cc
+++ b/components/history/core/browser/history_backend_db_unittest.cc
@@ -2877,5 +2877,27 @@
   EXPECT_EQ(segment_id2, results2[0]->GetID());
 }
 
+TEST_F(HistoryBackendDBTest, QuerySegmentUsageReturnsZeroScoreForZeroVisits) {
+  CreateBackendAndDatabase();
+
+  const GURL url("http://www.foo.com");
+  const base::Time time(base::Time::Now());
+
+  URLID url_id = db_->AddURL(URLRow(url));
+  ASSERT_NE(0, url_id);
+
+  SegmentID segment_id =
+      db_->CreateSegment(url_id, VisitSegmentDatabase::ComputeSegmentName(url));
+  ASSERT_NE(0, segment_id);
+  ASSERT_TRUE(db_->IncreaseSegmentVisitCount(segment_id, time, 0));
+
+  std::vector<std::unique_ptr<PageUsageData>> results =
+      db_->QuerySegmentUsage(/*max_result_count=*/1, base::NullCallback());
+  ASSERT_EQ(1u, results.size());
+  EXPECT_EQ(url, results[0]->GetURL());
+  EXPECT_EQ(segment_id, results[0]->GetID());
+  EXPECT_EQ(0, results[0]->GetScore());
+}
+
 }  // namespace
 }  // namespace history
diff --git a/components/history/core/browser/visitsegment_database.cc b/components/history/core/browser/visitsegment_database.cc
index 98c5628..f7c1437d 100644
--- a/components/history/core/browser/visitsegment_database.cc
+++ b/components/history/core/browser/visitsegment_database.cc
@@ -228,7 +228,9 @@
     int days_ago = (now - timeslot).InDays();
 
     // Score for this day in isolation.
-    float day_visits_score = 1.0f + log(static_cast<float>(visit_count));
+    float day_visits_score = visit_count <= 0.0f
+                                 ? 0.0f
+                                 : 1.0f + log(static_cast<float>(visit_count));
     // Recent visits count more than historical ones, so we multiply in a boost
     // related to how long ago this day was.
     // This boost is a curve that smoothly goes through these values:
diff --git a/components/metrics/demographics/demographic_metrics_provider_unittest.cc b/components/metrics/demographics/demographic_metrics_provider_unittest.cc
index 08468e4..ab0ce6f 100644
--- a/components/metrics/demographics/demographic_metrics_provider_unittest.cc
+++ b/components/metrics/demographics/demographic_metrics_provider_unittest.cc
@@ -35,7 +35,10 @@
   SYNC_FEATURE_NOT_ENABLED,
   SYNC_FEATURE_ENABLED,
   SYNC_FEATURE_ENABLED_BUT_PAUSED,
-  SYNC_FEATURE_TEMPORARILY_DISABLED,
+  // Represents the user clearing sync data via dashboard. On all platforms
+  // except ChromeOS (Ash), this clears the primary account (which is basically
+  // SYNC_FEATURE_NOT_ENABLED). On ChromeOS Ash, Sync enters a special state.
+  SYNC_FEATURE_DISABLED_ON_CHROMEOS_ASH_VIA_DASHBOARD,
 };
 
 // Profile client for testing that gets fake Profile information and services.
@@ -68,7 +71,6 @@
         // TestSyncService by default behaves as everything enabled/active.
         sync_service_ = std::make_unique<syncer::TestSyncService>();
 
-        CHECK(sync_service_->GetUserSettings()->IsSyncRequested());
         CHECK(sync_service_->GetDisableReasons().Empty());
         CHECK_EQ(syncer::SyncService::TransportState::ACTIVE,
                  sync_service_->GetTransportState());
@@ -79,21 +81,23 @@
         // Mimic the user signing out from content are (sync paused).
         sync_service_->SetPersistentAuthError();
 
-        CHECK(sync_service_->GetUserSettings()->IsSyncRequested());
         CHECK(sync_service_->GetDisableReasons().Empty());
         CHECK_EQ(syncer::SyncService::TransportState::PAUSED,
                  sync_service_->GetTransportState());
         break;
 
-      case SYNC_FEATURE_TEMPORARILY_DISABLED:
+      case SYNC_FEATURE_DISABLED_ON_CHROMEOS_ASH_VIA_DASHBOARD:
         sync_service_ = std::make_unique<syncer::TestSyncService>();
-        // Temporarily disable sync without turning it off.
-        sync_service_->GetUserSettings()->ClearSyncRequested();
+        sync_service_->SetDisableReasons(syncer::SyncService::DisableReasonSet(
+            syncer::SyncService::DISABLE_REASON_USER_CHOICE));
 
-        CHECK(!sync_service_->GetUserSettings()->IsSyncRequested());
-        CHECK(syncer::SyncService::DisableReasonSet(
-                  syncer::SyncService::DISABLE_REASON_USER_CHOICE) ==
-              sync_service_->GetDisableReasons());
+        // On ChromeOS Ash, IsFirstSetupComplete gets cleared temporarily but
+        // immediately afterwards, it gets set again with
+        // ENGINE_INITIALIZED_WITH_AUTO_START. And yet, IsSyncFeatureEnabled()
+        // stays false because the user needs to manually resume sync the
+        // feature.
+        CHECK(sync_service_->GetUserSettings()->IsFirstSetupComplete());
+        CHECK(!sync_service_->IsSyncFeatureEnabled());
         break;
     }
   }
@@ -201,12 +205,14 @@
                                UserDemographicsStatus::kSyncNotEnabled, 1);
 }
 
-TEST(DemographicMetricsProviderTest,
-     ProvideSyncedUserNoisedBirthYearAndGender_SyncTemporarilyDisabled) {
+TEST(
+    DemographicMetricsProviderTest,
+    ProvideSyncedUserNoisedBirthYearAndGender_SyncFeatureDisabledOnChromeOsAshViaSyncDashboard) {
   base::HistogramTester histogram;
 
   auto client = std::make_unique<TestProfileClient>(
-      /*number_of_profiles=*/1, SYNC_FEATURE_TEMPORARILY_DISABLED);
+      /*number_of_profiles=*/1,
+      SYNC_FEATURE_DISABLED_ON_CHROMEOS_ASH_VIA_DASHBOARD);
 
   // Run demographics provider.
   DemographicMetricsProvider provider(
diff --git a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
index 7628504..d00273e 100644
--- a/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
+++ b/components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.cc
@@ -461,6 +461,9 @@
     case content::PrerenderTriggerType::kSpeculationRule:
       DCHECK(embedder_histogram_suffix_.empty());
       return histogram_name + ".SpeculationRule";
+    case content::PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+      DCHECK(embedder_histogram_suffix_.empty());
+      return histogram_name + ".SpeculationRuleFromIsolatedWorld";
     case content::PrerenderTriggerType::kEmbedder:
       DCHECK(!embedder_histogram_suffix_.empty());
       return histogram_name + ".Embedder_" + embedder_histogram_suffix_;
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
index cb0067c..10d0da79 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager.cc
@@ -198,6 +198,8 @@
 void InsecureCredentialsManager::OnReuseCheckDone(
     base::ElapsedTimer timer_since_reuse_check_start,
     base::flat_set<std::u16string> reused_passwords) {
+  base::UmaHistogramTimes("PasswordManager.ReuseCheck.Time",
+                          timer_since_reuse_check_start.Elapsed());
   reused_passwords_ = std::move(reused_passwords);
   NotifyInsecureCredentialsChanged();
 }
diff --git a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
index 8f9444b..fb94124 100644
--- a/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
+++ b/components/password_manager/core/browser/ui/insecure_credentials_manager_unittest.cc
@@ -1149,10 +1149,14 @@
   store().AddLogin(form2);
   RunUntilIdle();
   provider().StartReuseCheck();
+  AdvanceClock(base::Milliseconds(kDelay));
   RunUntilIdle();
 
   EXPECT_THAT(provider().GetInsecureCredentialEntries(),
               ElementsAre(CredentialUIEntry(form1), CredentialUIEntry(form2)));
+
+  histogram_tester().ExpectUniqueSample("PasswordManager.ReuseCheck.Time",
+                                        kDelay, 1);
 }
 
 TEST_F(InsecureCredentialsManagerTest, UpdatingReusedPasswordFixesTheIssue) {
diff --git a/components/password_manager/core/browser/ui/reuse_check_utility.cc b/components/password_manager/core/browser/ui/reuse_check_utility.cc
index 967d885..cf1d5536 100644
--- a/components/password_manager/core/browser/ui/reuse_check_utility.cc
+++ b/components/password_manager/core/browser/ui/reuse_check_utility.cc
@@ -7,6 +7,7 @@
 
 #include <unordered_map>
 
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/string_util.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/psl_matching_helper.h"
@@ -136,6 +137,11 @@
     }
     reused_passwords.insert(password);
   }
+
+  base::UmaHistogramCounts1000("PasswordManager.ReuseCheck.CheckedPasswords",
+                               password_to_credentials.size());
+  base::UmaHistogramCounts1000("PasswordManager.ReuseCheck.ReusedPasswords",
+                               reused_passwords.size());
   return reused_passwords;
 }
 
diff --git a/components/password_manager/core/browser/ui/reuse_check_utility_unittest.cc b/components/password_manager/core/browser/ui/reuse_check_utility_unittest.cc
index 4cb0d57..044189b5 100644
--- a/components/password_manager/core/browser/ui/reuse_check_utility_unittest.cc
+++ b/components/password_manager/core/browser/ui/reuse_check_utility_unittest.cc
@@ -5,6 +5,7 @@
 #include "components/password_manager/core/browser/ui/reuse_check_utility.h"
 
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "components/password_manager/core/browser/affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/ui/credential_ui_entry.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -44,12 +45,21 @@
 }
 
 TEST(ReuseCheckUtilityTest, ReuseDetected) {
+  base::HistogramTester histogram_tester;
+
   std::vector<CredentialUIEntry> credentials;
   credentials.push_back(
       CreateCredential(u"user1", u"password", {"https://test1.com"}));
   credentials.push_back(
       CreateCredential(u"user2", u"password", {"https://test2.com"}));
+  credentials.push_back(
+      CreateCredential(u"user", u"password2", {"https://test3.com"}));
   EXPECT_THAT(BulkReuseCheck(credentials, {}), ElementsAre(u"password"));
+
+  histogram_tester.ExpectUniqueSample(
+      "PasswordManager.ReuseCheck.CheckedPasswords", 2, 1);
+  histogram_tester.ExpectUniqueSample(
+      "PasswordManager.ReuseCheck.ReusedPasswords", 1, 1);
 }
 
 TEST(ReuseCheckUtilityTest, ReuseDetectedSameWebsite) {
diff --git a/components/policy/core/common/cloud/affiliation.cc b/components/policy/core/common/cloud/affiliation.cc
index 845be1b9..86e1a87 100644
--- a/components/policy/core/common/cloud/affiliation.cc
+++ b/components/policy/core/common/cloud/affiliation.cc
@@ -4,6 +4,8 @@
 
 #include "components/policy/core/common/cloud/affiliation.h"
 
+#include "components/policy/core/common/device_local_account_type.h"
+
 namespace policy {
 
 bool IsAffiliated(const base::flat_set<std::string>& user_ids,
@@ -15,4 +17,21 @@
   return false;
 }
 
+bool IsUserAffiliated(const base::flat_set<std::string>& user_affiliation_ids,
+                      const base::flat_set<std::string>& device_affiliation_ids,
+                      base::StringPiece email) {
+  // An empty username means incognito user in case of Chrome OS and no
+  // logged-in user in case of Chrome (SigninService). Many tests use nonsense
+  // email addresses (e.g. 'test') so treat those as non-enterprise users.
+  if (email.empty() || email.find('@') == base::StringPiece::npos) {
+    return false;
+  }
+
+  if (IsDeviceLocalAccountUser(email)) {
+    return true;
+  }
+
+  return IsAffiliated(user_affiliation_ids, device_affiliation_ids);
+}
+
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/affiliation.h b/components/policy/core/common/cloud/affiliation.h
index e51693b6..25d667ddc 100644
--- a/components/policy/core/common/cloud/affiliation.h
+++ b/components/policy/core/common/cloud/affiliation.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/containers/flat_set.h"
+#include "base/strings/string_piece.h"
 #include "components/policy/policy_export.h"
 
 namespace policy {
@@ -19,6 +20,16 @@
 POLICY_EXPORT bool IsAffiliated(const base::flat_set<std::string>& user_ids,
                                 const base::flat_set<std::string>& device_ids);
 
+// TODO(peletskyi): Remove email after affiliation based implementation will
+// fully work. http://crbug.com/515476
+// The function makes a decision if user with `user_affiliation_ids` and
+// `email` is affiliated on the device with `device_affiliation_ids` and
+// `enterprise_domain`.
+POLICY_EXPORT bool IsUserAffiliated(
+    const base::flat_set<std::string>& user_affiliation_ids,
+    const base::flat_set<std::string>& device_affiliation_ids,
+    base::StringPiece email);
+
 }  // namespace policy
 
 #endif  // COMPONENTS_POLICY_CORE_COMMON_CLOUD_AFFILIATION_H_
diff --git a/components/policy/core/common/cloud/affiliation_unittest.cc b/components/policy/core/common/cloud/affiliation_unittest.cc
index 874b938..9c7959b 100644
--- a/components/policy/core/common/cloud/affiliation_unittest.cc
+++ b/components/policy/core/common/cloud/affiliation_unittest.cc
@@ -57,4 +57,26 @@
   EXPECT_FALSE(IsAffiliated(user_ids, device_ids));
 }
 
+TEST(CloudManagementAffiliationTest, UserAffiliated) {
+  base::flat_set<std::string> user_ids;
+  base::flat_set<std::string> device_ids;
+
+  // Empty affiliation IDs.
+  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
+
+  user_ids.insert("aaaa");  // Only user affiliation IDs present.
+  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
+
+  device_ids.insert("bbbb");  // Device and user IDs do not overlap.
+  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
+
+  user_ids.insert("cccc");  // Device and user IDs do overlap.
+  device_ids.insert("cccc");
+  EXPECT_TRUE(IsUserAffiliated(user_ids, device_ids, "user@managed.com"));
+
+  // Invalid email overrides match of affiliation IDs.
+  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, ""));
+  EXPECT_FALSE(IsUserAffiliated(user_ids, device_ids, "user"));
+}
+
 }  // namespace policy
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/WebAppSettings.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/WebAppSettings.yaml
index 0db5bbfc..8424f07 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/WebAppSettings.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/WebAppSettings.yaml
@@ -4,6 +4,7 @@
 
   The <ph name="MANIFEST_ID_FIELD">manifest_id</ph> field is the Manifest ID for the Web App. See <ph name="WEB_APP_ID_REFERENCE_URL">https://developer.chrome.com/blog/pwa-manifest-id/<ex>https://developer.chrome.com/blog/pwa-manifest-id/</ex></ph> for instructions on how to determine the Manifest ID for an installed web app.
   The <ph name="RUN_ON_OS_LOGIN_FIELD">run_on_os_login</ph> field specifies if a web app can be run during OS login. If this field is set to <ph name="BLOCKED">blocked</ph>, the web app will not run during OS login and the user will not be able to enable this later. If this field is set to <ph name="RUN_WINDOWED">run_windowed</ph>, the web app will run during OS login and the user will not be able to disable this later. If this field is set to <ph name="ALLOWED">allowed</ph>, the user will be able to configure the web app to run at OS login. The default configuration only allows the <ph name="ALLOWED">allowed</ph> and <ph name="BLOCKED">blocked</ph> values.      "
+  (Since M114) The <ph name="PREVENT_CLOSE_FIELD">prevent_close</ph> field specifies if a web app shall be prevented from closing in any way (e.g. by the user, task manager, web APIs). This behavior can only be enabled if <ph name="RUN_ON_OS_LOGIN_FIELD">run_on_os_login</ph> is set to <ph name="RUN_WINDOWED">run_windowed</ph>. If the app were already running, this property will only come into effect after the app is restarted. If this field is not defined, apps will be closable by users.
 example_value:
 - manifest_id: https://foo.example/index.html
   run_on_os_login: allowed
@@ -11,6 +12,7 @@
   run_on_os_login: allowed
 - manifest_id: https://foobar.example/index.html
   run_on_os_login: run_windowed
+  prevent_close: true
 - manifest_id: '*'
   run_on_os_login: blocked
 features:
@@ -32,6 +34,8 @@
         - blocked
         - run_windowed
         type: string
+      prevent_close:
+        type: boolean
     required:
     - manifest_id
     type: object
diff --git a/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc b/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
index 02ba946..8d0fd6b 100644
--- a/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
+++ b/components/safe_browsing/core/browser/realtime/url_lookup_service_base.cc
@@ -258,10 +258,12 @@
 
   base::TimeTicks get_cache_start_time = base::TimeTicks::Now();
 
+  absl::optional<bool> is_verdict_from_past_session;
   RTLookupResponse::ThreatInfo::VerdictType verdict_type =
-      cache_manager_ ? cache_manager_->GetCachedRealTimeUrlVerdict(
-                           url, cached_threat_info.get())
-                     : RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED;
+      cache_manager_
+          ? cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, cached_threat_info.get(), &is_verdict_from_past_session)
+          : RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED;
 
   RecordSparseWithAndWithoutSuffix("SafeBrowsing.RT.GetCacheResult",
                                    GetMetricSuffix(), verdict_type);
@@ -271,6 +273,12 @@
 
   if (verdict_type == RTLookupResponse::ThreatInfo::SAFE ||
       verdict_type == RTLookupResponse::ThreatInfo::DANGEROUS) {
+    if (is_verdict_from_past_session.has_value()) {
+      base::UmaHistogramBoolean(
+          "SafeBrowsing.RT.GetCacheResultIsFromPastSession",
+          is_verdict_from_past_session.value());
+    }
+
     auto cache_response = std::make_unique<RTLookupResponse>();
     RTLookupResponse::ThreatInfo* new_threat_info =
         cache_response->add_threat_info();
diff --git a/components/safe_browsing/core/browser/verdict_cache_manager.cc b/components/safe_browsing/core/browser/verdict_cache_manager.cc
index b804d23..d86690e 100644
--- a/components/safe_browsing/core/browser/verdict_cache_manager.cc
+++ b/components/safe_browsing/core/browser/verdict_cache_manager.cc
@@ -290,7 +290,9 @@
     scoped_refptr<HostContentSettingsMap> content_settings,
     const ContentSettingsType contents_setting_type,
     const char* proto_name,
-    MatchParams match_params) {
+    MatchParams match_params,
+    base::Time& time_initialized,
+    absl::optional<bool>* out_is_verdict_from_past_initialization) {
   DCHECK(proto_name == kVerdictProto || proto_name == kRealTimeThreatInfoProto);
 
   absl::optional<base::Value> result;
@@ -341,6 +343,13 @@
         PathVariantsMatchCacheExpression(paths, cache_expression_path) &&
         match_params.ShouldMatch() &&
         !IsCacheExpired(verdict_received_time, verdict.cache_duration_sec())) {
+      // We cast to an int because that is initially done for
+      // verdict_received_time. If we don't, then the comparison could
+      // incorrectly claim that the verdict was received before initialization
+      // simply because of the int casting.
+      *out_is_verdict_from_past_initialization =
+          verdict_received_time <
+          static_cast<int>(time_initialized.ToDoubleT());
       max_path_depth = path_depth;
       result = std::move(value);
     }
@@ -356,7 +365,9 @@
     const std::string& type_key,
     scoped_refptr<HostContentSettingsMap> content_settings,
     const ContentSettingsType contents_setting_type,
-    const char* proto_name) {
+    const char* proto_name,
+    base::Time& time_initialized,
+    absl::optional<bool>* out_is_verdict_from_past_initialization) {
   DCHECK(proto_name == kVerdictProto || proto_name == kRealTimeThreatInfoProto);
   absl::optional<base::Value> most_matching_verdict;
   MatchParams match_params;
@@ -371,13 +382,17 @@
     int depth = static_cast<int>(GetHostDepth(host));
     GURL url_to_check = GetUrlWithHostAndPath(host, root_path);
     match_params.is_exact_host = (root_host == host);
+    absl::optional<bool> is_verdict_from_past_initialization;
     absl::optional<base::Value> verdict =
         GetMostMatchingCachedVerdictEntryWithPathMatching<T>(
             url_to_check, type_key, content_settings, contents_setting_type,
-            proto_name, match_params);
+            proto_name, match_params, time_initialized,
+            &is_verdict_from_past_initialization);
     if (depth > max_path_depth && verdict && verdict->is_dict()) {
       max_path_depth = depth;
       most_matching_verdict = std::move(verdict);
+      *out_is_verdict_from_past_initialization =
+          is_verdict_from_past_initialization;
     }
   }
 
@@ -428,6 +443,7 @@
       stored_verdict_count_real_time_url_check_(absl::nullopt),
       content_settings_(content_settings),
       sync_observer_(std::move(sync_observer)) {
+  time_initialized_ = base::Time::Now();
   if (history_service) {
     history_service_observation_.Observe(history_service);
   }
@@ -540,11 +556,13 @@
 
   std::string type_key =
       GetKeyOfTypeFromTriggerType(trigger_type, password_type);
+  absl::optional<bool> is_verdict_from_past_initialization;
   absl::optional<base::Value> most_matching_verdict =
       GetMostMatchingCachedVerdictEntryWithHostAndPathMatching<
           LoginReputationClientResponse>(
           url, type_key, content_settings_,
-          ContentSettingsType::PASSWORD_PROTECTION, kVerdictProto);
+          ContentSettingsType::PASSWORD_PROTECTION, kVerdictProto,
+          time_initialized_, &is_verdict_from_past_initialization);
 
   return GetVerdictTypeFromMostMatchedCachedVerdict<
       LoginReputationClientResponse>(
@@ -678,7 +696,8 @@
 RTLookupResponse::ThreatInfo::VerdictType
 VerdictCacheManager::GetCachedRealTimeUrlVerdict(
     const GURL& url,
-    RTLookupResponse::ThreatInfo* out_threat_info) {
+    RTLookupResponse::ThreatInfo* out_threat_info,
+    absl::optional<bool>* out_is_verdict_from_past_initialization) {
   if (is_shut_down_) {
     return RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED;
   }
@@ -688,7 +707,8 @@
           RTLookupResponse::ThreatInfo>(
           url, kRealTimeUrlCacheKey, content_settings_,
           ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA,
-          kRealTimeThreatInfoProto);
+          kRealTimeThreatInfoProto, time_initialized_,
+          out_is_verdict_from_past_initialization);
 
   return GetVerdictTypeFromMostMatchedCachedVerdict<
       RTLookupResponse::ThreatInfo>(kRealTimeThreatInfoProto,
@@ -703,12 +723,14 @@
     return safe_browsing::ClientSideDetectionType::
         CLIENT_SIDE_DETECTION_TYPE_UNSPECIFIED;
   }
+  absl::optional<bool> is_verdict_from_past_initialization;
   absl::optional<base::Value> most_matching_verdict =
       GetMostMatchingCachedVerdictEntryWithHostAndPathMatching<
           RTLookupResponse::ThreatInfo>(
           url, kRealTimeUrlCacheKey, content_settings_,
           ContentSettingsType::SAFE_BROWSING_URL_CHECK_DATA,
-          kRealTimeThreatInfoProto);
+          kRealTimeThreatInfoProto, time_initialized_,
+          &is_verdict_from_past_initialization);
 
   if (!most_matching_verdict || !most_matching_verdict->is_dict()) {
     return safe_browsing::ClientSideDetectionType::
diff --git a/components/safe_browsing/core/browser/verdict_cache_manager.h b/components/safe_browsing/core/browser/verdict_cache_manager.h
index edcc7c63..4b085a4a 100644
--- a/components/safe_browsing/core/browser/verdict_cache_manager.h
+++ b/components/safe_browsing/core/browser/verdict_cache_manager.h
@@ -85,10 +85,14 @@
   // Looks up |content_settings_| to find the cached verdict response. If
   // verdict is not available or is expired, return VERDICT_TYPE_UNSPECIFIED.
   // Otherwise, the most matching theat info will be copied to out_threat_info.
-  // Can be called on any thread.
+  // |out_is_verdict_from_past_initialization| represents whether the verdict
+  // was set before the current VerdictCacheManager instance was initialized,
+  // and is used only for logging. The parameter is only set if the unexpired
+  // cache entry was found. Can be called on any thread.
   RTLookupResponse::ThreatInfo::VerdictType GetCachedRealTimeUrlVerdict(
       const GURL& url,
-      RTLookupResponse::ThreatInfo* out_threat_info);
+      RTLookupResponse::ThreatInfo* out_threat_info,
+      absl::optional<bool>* out_is_verdict_from_past_initialization);
 
   safe_browsing::ClientSideDetectionType
   GetCachedRealTimeUrlClientSideDetectionType(const GURL& url);
@@ -232,6 +236,9 @@
 
   bool is_shut_down_ = false;
 
+  // Represents the time the VerdictCacheManager object was constructed.
+  base::Time time_initialized_;
+
   base::WeakPtrFactory<VerdictCacheManager> weak_factory_{this};
 
   static bool has_artificial_unsafe_url_;
diff --git a/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc b/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
index 74dfc433..f0c3c00 100644
--- a/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
+++ b/components/safe_browsing/core/browser/verdict_cache_manager_unittest.cc
@@ -57,6 +57,10 @@
         &test_pref_service_, false /* is_off_the_record */,
         false /* store_last_modified */, false /* restore_session */,
         false /* should_record_metrics */);
+    InitializeVerdictCacheManager();
+  }
+
+  void InitializeVerdictCacheManager() {
     auto sync_observer = std::make_unique<MockSafeBrowsingSyncObserver>();
     raw_sync_observer_ = sync_observer.get();
     cache_manager_ = std::make_unique<VerdictCacheManager>(
@@ -477,16 +481,18 @@
                 password_type, &actual_verdict));
 
   RTLookupResponse::ThreatInfo actual_real_time_threat_info;
+  absl::optional<bool> is_verdict_from_past_initialization;
   // No cached SAFE_BROWSING_URL_CHECK_DATA verdict for www.example.com/.
-  EXPECT_EQ(
-      RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
-      cache_manager_->GetCachedRealTimeUrlVerdict(
-          GURL("https://www.example.com/"), &actual_real_time_threat_info));
+  EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                GURL("https://www.example.com/"), &actual_real_time_threat_info,
+                &is_verdict_from_past_initialization));
   // Has cached SAFE_BROWSING_URL_CHECK_DATA verdict for www.example.com/path.
   EXPECT_EQ(
       RTLookupResponse::ThreatInfo::DANGEROUS,
       cache_manager_->GetCachedRealTimeUrlVerdict(
-          GURL("https://www.example.com/path"), &actual_real_time_threat_info));
+          GURL("https://www.example.com/path"), &actual_real_time_threat_info,
+          &is_verdict_from_past_initialization));
 
   // token1 is cleaned up.
   EXPECT_FALSE(
@@ -571,13 +577,16 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
   EXPECT_EQ("www.example.com/path",
             out_verdict.cache_expression_using_match_type());
   EXPECT_EQ(60, out_verdict.cache_duration_sec());
   EXPECT_EQ(RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING,
             out_verdict.threat_type());
+  EXPECT_FALSE(is_verdict_from_past_initialization.value());
   histograms.ExpectUniqueSample(
       "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount",
       /* sample */ 2, /* expected_count */ 1);
@@ -608,19 +617,25 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url1, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url1, &out_verdict, &is_verdict_from_past_initialization));
   EXPECT_EQ("www.example.com/",
             out_verdict.cache_expression_using_match_type());
   EXPECT_EQ(RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING,
             out_verdict.threat_type());
+  EXPECT_FALSE(is_verdict_from_past_initialization.value());
 
+  absl::optional<bool> is_verdict_from_past_initialization2;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url2, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url2, &out_verdict, &is_verdict_from_past_initialization2));
   EXPECT_EQ("www.example.com/path",
             out_verdict.cache_expression_using_match_type());
   EXPECT_EQ(RTLookupResponse::ThreatInfo::UNWANTED_SOFTWARE,
             out_verdict.threat_type());
+  EXPECT_FALSE(is_verdict_from_past_initialization2.value());
 }
 
 TEST_F(VerdictCacheManagerTest,
@@ -635,8 +650,11 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
+  EXPECT_FALSE(is_verdict_from_past_initialization.has_value());
 }
 
 TEST_F(VerdictCacheManagerTest,
@@ -650,8 +668,11 @@
                           RTLookupResponse::ThreatInfo::EXACT_MATCH);
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
+  EXPECT_FALSE(is_verdict_from_past_initialization.value());
 
   history::URLRows deleted_urls;
   deleted_urls.push_back(history::URLRow(GURL("https://www.example.com/path")));
@@ -659,8 +680,11 @@
   cache_manager_->RemoveContentSettingsOnURLsDeleted(false /* all_history */,
                                                      deleted_urls);
   EXPECT_EQ(0u, cache_manager_->GetStoredRealTimeUrlCheckVerdictCount());
+  absl::optional<bool> is_verdict_from_past_initialization2;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization2));
+  EXPECT_FALSE(is_verdict_from_past_initialization2.has_value());
 }
 
 TEST_F(VerdictCacheManagerTest,
@@ -685,13 +709,16 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::SUSPICIOUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
   EXPECT_EQ("www.example.com/path",
             out_verdict.cache_expression_using_match_type());
   EXPECT_EQ(60, out_verdict.cache_duration_sec());
   EXPECT_EQ(RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING,
             out_verdict.threat_type());
+  EXPECT_FALSE(is_verdict_from_past_initialization.value());
   EXPECT_EQ(static_cast<int>(safe_browsing::ClientSideDetectionType::
                                  CLIENT_SIDE_DETECTION_TYPE_UNSPECIFIED),
             cache_manager_->GetCachedRealTimeUrlClientSideDetectionType(url));
@@ -742,9 +769,11 @@
                           RTLookupResponse::ThreatInfo::COVERING_MATCH);
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
             cache_manager_->GetCachedRealTimeUrlVerdict(
-                GURL("https://b.example.test/path/path2"), &out_verdict));
+                GURL("https://b.example.test/path/path2"), &out_verdict,
+                &is_verdict_from_past_initialization));
 }
 
 TEST_F(VerdictCacheManagerTest, TestHostSuffixMatchingMostExactMatching) {
@@ -782,14 +811,17 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
             cache_manager_->GetCachedRealTimeUrlVerdict(
-                GURL("https://a.example.test/path1/"), &out_verdict));
+                GURL("https://a.example.test/path1/"), &out_verdict,
+                &is_verdict_from_past_initialization));
   // Since |cache_expression_exact_matching| is set to EXACT_MATCH, cache is not
   // found.
   EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
             cache_manager_->GetCachedRealTimeUrlVerdict(
-                GURL("https://a.example.test/path1/path2"), &out_verdict));
+                GURL("https://a.example.test/path1/path2"), &out_verdict,
+                &is_verdict_from_past_initialization));
 }
 
 TEST_F(VerdictCacheManagerTest, TestMatchingTypeNotSet) {
@@ -807,9 +839,11 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
 
   RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   // If |cache_expression_match_type| is not set, ignore this cache.
   EXPECT_EQ(RTLookupResponse::ThreatInfo::VERDICT_TYPE_UNSPECIFIED,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
   histograms.ExpectBucketCount(
       "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount",
       /* sample */ 0, /* expected_count */ 1);
@@ -819,7 +853,8 @@
   cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
   // Should be able to get the cache if |cache_expression_match_type| is set.
   EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
-            cache_manager_->GetCachedRealTimeUrlVerdict(url, &out_verdict));
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
   histograms.ExpectBucketCount(
       "SafeBrowsing.RT.CacheManager.RealTimeVerdictCount",
       /* sample */ 1, /* expected_count */ 1);
@@ -950,8 +985,10 @@
   // Call to cache_manager after shutdown should not cause a crash.
   cache_manager_->CacheRealTimeUrlVerdict(rt_response, base::Time::Now());
   RTLookupResponse::ThreatInfo out_rt_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
   cache_manager_->GetCachedRealTimeUrlVerdict(
-      GURL("https://www.example.com/path"), &out_rt_verdict);
+      GURL("https://www.example.com/path"), &out_rt_verdict,
+      &is_verdict_from_past_initialization);
   LoginReputationClientResponse pg_response;
   ReusedPasswordAccountType password_type;
   cache_manager_->CachePhishGuardVerdict(
@@ -982,4 +1019,36 @@
   EXPECT_TRUE(base::Contains(cache_results, "bbbb"));
 }
 
+TEST_F(VerdictCacheManagerTest, TestIsVerdictFromPastInitialization) {
+  GURL url("https://www.example.com/path");
+  RTLookupResponse response;
+  AddThreatInfoToResponse(response, RTLookupResponse::ThreatInfo::DANGEROUS,
+                          RTLookupResponse::ThreatInfo::SOCIAL_ENGINEERING, 60,
+                          "www.example.com/path",
+                          RTLookupResponse::ThreatInfo::EXACT_MATCH);
+  cache_manager_->CacheRealTimeUrlVerdict(response, base::Time::Now());
+
+  // First, confirm that the verdict is not from a past initialization if it has
+  // just been cached.
+  RTLookupResponse::ThreatInfo out_verdict;
+  absl::optional<bool> is_verdict_from_past_initialization;
+  EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict, &is_verdict_from_past_initialization));
+  EXPECT_FALSE(is_verdict_from_past_initialization.value());
+
+  // Re-create the verdict cache manager after a brief delay.
+  task_environment_.FastForwardBy(base::Seconds(10));
+  InitializeVerdictCacheManager();
+
+  // This time, the verdict should still be there, but it is from the past
+  // initialization.
+  RTLookupResponse::ThreatInfo out_verdict2;
+  absl::optional<bool> is_verdict_from_past_initialization2;
+  EXPECT_EQ(RTLookupResponse::ThreatInfo::DANGEROUS,
+            cache_manager_->GetCachedRealTimeUrlVerdict(
+                url, &out_verdict2, &is_verdict_from_past_initialization2));
+  EXPECT_TRUE(is_verdict_from_past_initialization2.value());
+}
+
 }  // namespace safe_browsing
diff --git a/components/segmentation_platform/embedder/default_model/device_tier_segment.cc b/components/segmentation_platform/embedder/default_model/device_tier_segment.cc
index 87165f92..976d051 100644
--- a/components/segmentation_platform/embedder/default_model/device_tier_segment.cc
+++ b/components/segmentation_platform/embedder/default_model/device_tier_segment.cc
@@ -99,7 +99,7 @@
   float score = 0;
   float device_ram_in_gb = inputs[0] / 1024;
   float device_os_version = inputs[1];
-  int device_ppi = inputs[2];
+  float device_ppi = inputs[2];
   if ((device_ram_in_gb >= 8 && device_os_version >= 10 && device_ppi > 370) ||
       (device_ram_in_gb >= 6 && device_os_version >= 11 && device_ppi > 370)) {
     score = 3;
diff --git a/components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.cc b/components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.cc
index 4643c6a..8de1088 100644
--- a/components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.cc
+++ b/components/segmentation_platform/embedder/default_model/tablet_productivity_user_model.cc
@@ -159,7 +159,7 @@
   int device_tier_score;
   float device_ram_in_gb = inputs[0] / 1024;
   float device_os_version = inputs[1];
-  int device_ppi = inputs[2];
+  float device_ppi = inputs[2];
   if ((device_ram_in_gb >= 8 && device_os_version >= 10 && device_ppi > 370) ||
       (device_ram_in_gb >= 6 && device_os_version >= 11 && device_ppi > 370)) {
     device_tier_score = 3;  // High-Tier Device
diff --git a/components/segmentation_platform/internal/execution/processing/custom_input_processor.cc b/components/segmentation_platform/internal/execution/processing/custom_input_processor.cc
index e5840a2..a65d562 100644
--- a/components/segmentation_platform/internal/execution/processing/custom_input_processor.cc
+++ b/components/segmentation_platform/internal/execution/processing/custom_input_processor.cc
@@ -291,7 +291,7 @@
   if (custom_input.tensor_length() != 1) {
     return false;
   }
-  int device_ram_in_mb = base::SysInfo::AmountOfPhysicalMemoryMB();
+  float device_ram_in_mb = base::SysInfo::AmountOfPhysicalMemoryMB();
   out_tensor.emplace_back(device_ram_in_mb);
   return true;
 }
@@ -303,7 +303,7 @@
     return false;
   }
   std::string os_version = base::SysInfo::OperatingSystemVersion();
-  int device_os_version = processing::ProcessOsVersionString(os_version);
+  float device_os_version = processing::ProcessOsVersionString(os_version);
   out_tensor.emplace_back(device_os_version);
   return true;
 }
@@ -315,7 +315,7 @@
     return false;
   }
 #if BUILDFLAG(IS_ANDROID)
-  int device_ppi = CustomDeviceUtils::GetDevicePPI();
+  float device_ppi = CustomDeviceUtils::GetDevicePPI();
   out_tensor.emplace_back(device_ppi);
   return true;
 #else
diff --git a/components/segmentation_platform/public/proto/model_metadata.proto b/components/segmentation_platform/public/proto/model_metadata.proto
index 2caf61d6..a780a48a 100644
--- a/components/segmentation_platform/public/proto/model_metadata.proto
+++ b/components/segmentation_platform/public/proto/model_metadata.proto
@@ -137,18 +137,18 @@
     FILL_SYNC_DEVICE_INFO = 5;
 
     // Output is a tensor of length 1 consisting device RAM in MB.
-    // Output type: int
+    // Output type: float
     // Output length: 1
     FILL_DEVICE_RAM_MB = 6;
 
     // Output is a tensor of length 1 describing device OS level.
-    // Output type: int
+    // Output type: float
     // Output length: 1
     FILL_DEVICE_OS_VERSION_NUMBER = 7;
 
     // Output is a tensor of length 1 giving pixels per inch for the current
     // device used by the user.
-    // Output type: int
+    // Output type: float
     // Output length: 1
     FILL_DEVICE_PPI = 8;
   }
diff --git a/components/signin/public/base/signin_pref_names.cc b/components/signin/public/base/signin_pref_names.cc
index 409d7a5..5f6f813 100644
--- a/components/signin/public/base/signin_pref_names.cc
+++ b/components/signin/public/base/signin_pref_names.cc
@@ -101,4 +101,8 @@
 const char kRestrictAccountsToPatterns[] =
     "signin.restrict_accounts_to_patterns";
 
+// Boolean which indicates if the user is allowed to sign into Chrome on the
+// next startup.
+const char kSigninAllowedOnNextStartup[] = "signin.allowed_on_next_startup";
+
 }  // namespace prefs
diff --git a/components/signin/public/base/signin_pref_names.h b/components/signin/public/base/signin_pref_names.h
index 1e9e64ec..3a00cda 100644
--- a/components/signin/public/base/signin_pref_names.h
+++ b/components/signin/public/base/signin_pref_names.h
@@ -32,6 +32,7 @@
 extern const char kSignedInWithCredentialProvider[];
 extern const char kSigninAllowed[];
 extern const char kGaiaCookieLastListAccountsData[];
+extern const char kSigninAllowedOnNextStartup[];
 
 }  // namespace prefs
 
diff --git a/components/sync/driver/sync_internals_util.cc b/components/sync/driver/sync_internals_util.cc
index cf1f0816..c361524 100644
--- a/components/sync/driver/sync_internals_util.cc
+++ b/components/sync/driver/sync_internals_util.cc
@@ -13,7 +13,6 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "build/chromeos_buildflags.h"
 #include "components/sync/base/time.h"
 #include "components/sync/driver/sync_service.h"
 #include "components/sync/driver/sync_token_status.h"
@@ -25,10 +24,6 @@
 #include "components/version_info/version_info.h"
 #include "url/gurl.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_features.h"
-#endif
-
 namespace syncer::sync_ui_util {
 
 namespace {
@@ -314,10 +309,6 @@
       section_summary->AddStringStat("User Actionable Error");
   Stat<std::string>* disable_reasons =
       section_summary->AddStringStat("Disable Reasons");
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  Stat<std::string>* os_feature_state =
-      section_summary->AddStringStat("Chrome OS Sync Feature");
-#endif
   Stat<bool>* feature_enabled =
       section_summary->AddBoolStat("Sync Feature Enabled");
   Stat<bool>* setup_in_progress =
@@ -452,9 +443,6 @@
                    /*is_good=*/user_actionable_error ==
                        SyncService::UserActionableError::kNone);
   disable_reasons->Set(GetDisableReasonsString(service->GetDisableReasons()));
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  os_feature_state->Set("Enforced Enabled");
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   feature_enabled->Set(service->IsSyncFeatureEnabled());
   setup_in_progress->Set(service->IsSetupInProgress());
   std::string auth_error_str = service->GetAuthError().ToString();
diff --git a/components/sync/driver/sync_service_impl_unittest.cc b/components/sync/driver/sync_service_impl_unittest.cc
index cb77d05..4dfacd6 100644
--- a/components/sync/driver/sync_service_impl_unittest.cc
+++ b/components/sync/driver/sync_service_impl_unittest.cc
@@ -473,7 +473,6 @@
 
   SyncPrefs sync_prefs(prefs());
 
-  ASSERT_TRUE(sync_prefs.IsSyncRequested());
   ASSERT_EQ(SyncService::DisableReasonSet(), service()->GetDisableReasons());
   ASSERT_EQ(SyncService::TransportState::ACTIVE,
             service()->GetTransportState());
@@ -492,7 +491,6 @@
   EXPECT_FALSE(service()->IsSyncFeatureEnabled());
 
   service()->GetUserSettings()->SetSyncRequested();
-  EXPECT_TRUE(sync_prefs.IsSyncRequested());
   EXPECT_EQ(SyncService::DisableReasonSet(), service()->GetDisableReasons());
   service()->GetUserSettings()->SetFirstSetupComplete(
       syncer::SyncFirstSetupCompleteSource::BASIC_FLOW);
@@ -540,7 +538,7 @@
   CreateService(SyncServiceImpl::MANUAL_START);
   InitializeForNthSync();
   ASSERT_TRUE(service()->GetUserSettings()->IsFirstSetupComplete());
-  ASSERT_TRUE(service()->GetUserSettings()->IsSyncRequested());
+  ASSERT_EQ(SyncService::DisableReasonSet(), service()->GetDisableReasons());
   ASSERT_EQ(0, component_factory()->clear_transport_data_call_count());
 
   // Sign-out.
@@ -554,11 +552,14 @@
   base::RunLoop().RunUntilIdle();
   // These are specific to sync-the-feature and should be cleared.
   EXPECT_FALSE(service()->GetUserSettings()->IsFirstSetupComplete());
-  EXPECT_FALSE(service()->GetUserSettings()->IsSyncRequested());
+  EXPECT_EQ(
+      SyncService::DisableReasonSet(SyncService::DISABLE_REASON_NOT_SIGNED_IN,
+                                    SyncService::DISABLE_REASON_USER_CHOICE),
+      service()->GetDisableReasons());
   EXPECT_EQ(1, component_factory()->clear_transport_data_call_count());
 }
 
-TEST_F(SyncServiceImplTest, SyncRequestedSetToFalseIfStartsSignedOut) {
+TEST_F(SyncServiceImplTest, DisableReasonUserChoiceIfStartsSignedOut) {
   // Set up bad state.
   SyncPrefs sync_prefs(prefs());
   sync_prefs.SetSyncRequested(true);
@@ -566,8 +567,8 @@
   CreateService(SyncServiceImpl::MANUAL_START);
   service()->Initialize();
 
-  // There's no signed-in user, so SyncRequested should have been set to false.
-  EXPECT_FALSE(service()->GetUserSettings()->IsSyncRequested());
+  EXPECT_TRUE(service()->GetDisableReasons().Has(
+      SyncService::DISABLE_REASON_USER_CHOICE));
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
@@ -792,7 +793,6 @@
   // Calling StopAndClear while already stopped should not crash. This may
   // (under some circumstances) happen when the user enables sync again but hits
   // the cancel button at the end of the process.
-  ASSERT_FALSE(service()->GetUserSettings()->IsSyncRequested());
   service()->StopAndClear();
   EXPECT_FALSE(service()->IsSyncFeatureEnabled());
 }
@@ -1068,18 +1068,16 @@
   EXPECT_EQ(SyncService::TransportState::ACTIVE,
             service()->GetTransportState());
 
+  // The transport should continue active even if kSyncManaged becomes true.
   prefs()->SetManagedPref(prefs::kSyncManaged, base::Value(true));
 
   EXPECT_EQ(SyncService::DisableReasonSet(), service()->GetDisableReasons());
   EXPECT_EQ(SyncService::TransportState::ACTIVE,
             service()->GetTransportState());
 
-  // Note: If standalone transport is enabled, then setting kSyncManaged to
-  // false will immediately start up the engine. Otherwise, the RequestStart
-  // call below will trigger it.
+  // Setting kSyncManaged back to false should also make no difference.
   prefs()->SetManagedPref(prefs::kSyncManaged, base::Value(false));
 
-  service()->GetUserSettings()->SetSyncRequested();
   EXPECT_EQ(SyncService::DisableReasonSet(), service()->GetDisableReasons());
   EXPECT_EQ(SyncService::TransportState::ACTIVE,
             service()->GetTransportState());
diff --git a/components/sync/driver/sync_user_settings.h b/components/sync/driver/sync_user_settings.h
index a86eb28..7ddfb03 100644
--- a/components/sync/driver/sync_user_settings.h
+++ b/components/sync/driver/sync_user_settings.h
@@ -36,17 +36,14 @@
  public:
   virtual ~SyncUserSettings() = default;
 
-  // Whether the user wants Sync to run. This is false by default, but gets set
-  // to true early in the Sync setup flow, after the user has pressed "turn on
-  // Sync" but before they have actually confirmed the settings (that's
-  // IsFirstSetupComplete()). After Sync is enabled, this can get set to false
-  // by the Sync feature toggle in settings, or when Sync gets reset from the
-  // dashboard. This maps to DISABLE_REASON_USER_CHOICE.
-  virtual bool IsSyncRequested() const = 0;
+  // Indicates that the initial Sync setup has started, usually meaning that the
+  // user clicked on a UI to turn Sync (the Feature) on. NOTE: On ChromeOS, this
+  // gets set automatically, so it doesn't really mean anything. See
+  // |browser_defaults::kSyncAutoStarts|.
   virtual void SetSyncRequested() = 0;
 
-  // Whether the initial Sync setup has been completed, meaning the user has
-  // consented to Sync.
+  // Whether the initial Sync Feature setup has been completed, meaning the
+  // user has turned on Sync-the-Feature.
   // NOTE: On ChromeOS, this gets set automatically, so it doesn't really mean
   // anything. See |browser_defaults::kSyncAutoStarts|.
   virtual bool IsFirstSetupComplete() const = 0;
diff --git a/components/sync/driver/sync_user_settings_impl.h b/components/sync/driver/sync_user_settings_impl.h
index ac2adfe7..0a67ca2 100644
--- a/components/sync/driver/sync_user_settings_impl.h
+++ b/components/sync/driver/sync_user_settings_impl.h
@@ -33,7 +33,6 @@
   ~SyncUserSettingsImpl() override;
 
   // SyncUserSettings implementation.
-  bool IsSyncRequested() const override;
   void SetSyncRequested() override;
   bool IsFirstSetupComplete() const override;
   void SetFirstSetupComplete(SyncFirstSetupCompleteSource source) override;
@@ -70,6 +69,15 @@
   void SetDecryptionNigoriKey(std::unique_ptr<Nigori> nigori) override;
   std::unique_ptr<Nigori> GetDecryptionNigoriKey() const override;
 
+  // Whether the user wants Sync to run. This is false by default, but gets set
+  // to true early in the Sync setup flow, after the user has pressed "turn on
+  // Sync" but before they have actually confirmed the settings (that's
+  // IsFirstSetupComplete()). After Sync is enabled, this can get set to false
+  // via signout (which also clears IsFirstSetupComplete) or, on ChromeOS Ash,
+  // when Sync gets reset from the dashboard.
+  //
+  // This maps to DISABLE_REASON_USER_CHOICE.
+  bool IsSyncRequested() const;
   void ClearSyncRequested();
   void SetSyncRequestedIfNotSetExplicitly();
 
diff --git a/components/sync/protocol/password_specifics.proto b/components/sync/protocol/password_specifics.proto
index 57c40ae..02d41f8d 100644
--- a/components/sync/protocol/password_specifics.proto
+++ b/components/sync/protocol/password_specifics.proto
@@ -70,6 +70,14 @@
 
     // Whether the issue was muted by user.
     optional bool is_muted = 2;
+
+    // Whether the backend should notify the user about this issue.
+    // Set to true if the user hasn't already seen a client notification for
+    // this issue (e.g. a leak detection prompt in Chrome). The backend sending
+    // notifications does not reset this field. All other sources can write this
+    // in both `PasswordSpecificsData` and `PasswordSpecificsMetadata` and do
+    // so.
+    optional bool trigger_notification_from_backend_on_detection = 3;
   }
   optional PasswordIssue leaked_password_issue = 1;
   optional PasswordIssue reused_password_issue = 2;
diff --git a/components/sync/protocol/proto_visitors.h b/components/sync/protocol/proto_visitors.h
index af754557..f0558010 100644
--- a/components/sync/protocol/proto_visitors.h
+++ b/components/sync/protocol/proto_visitors.h
@@ -885,6 +885,7 @@
 VISIT_PROTO_FIELDS(const sync_pb::PasswordIssues_PasswordIssue& proto) {
   VISIT(date_first_detection_windows_epoch_micros);
   VISIT(is_muted);
+  VISIT(trigger_notification_from_backend_on_detection);
 }
 
 VISIT_PROTO_FIELDS(const sync_pb::PasswordSpecificsData_Notes& proto) {
diff --git a/components/sync/test/sync_user_settings_mock.h b/components/sync/test/sync_user_settings_mock.h
index b4651b8..5d70c797 100644
--- a/components/sync/test/sync_user_settings_mock.h
+++ b/components/sync/test/sync_user_settings_mock.h
@@ -19,7 +19,6 @@
  public:
   SyncUserSettingsMock();
   ~SyncUserSettingsMock() override;
-  MOCK_METHOD(bool, IsSyncRequested, (), (const override));
   MOCK_METHOD(void, SetSyncRequested, (), (override));
   MOCK_METHOD(bool, IsFirstSetupComplete, (), (const override));
   MOCK_METHOD(void,
diff --git a/components/sync/test/test_sync_user_settings.cc b/components/sync/test/test_sync_user_settings.cc
index bf55b67..9038fac 100644
--- a/components/sync/test/test_sync_user_settings.cc
+++ b/components/sync/test/test_sync_user_settings.cc
@@ -45,22 +45,12 @@
 
 TestSyncUserSettings::~TestSyncUserSettings() = default;
 
-bool TestSyncUserSettings::IsSyncRequested() const {
-  return !service_->HasDisableReason(SyncService::DISABLE_REASON_USER_CHOICE);
-}
-
 void TestSyncUserSettings::SetSyncRequested() {
   SyncService::DisableReasonSet disable_reasons = service_->GetDisableReasons();
   disable_reasons.Remove(SyncService::DISABLE_REASON_USER_CHOICE);
   service_->SetDisableReasons(disable_reasons);
 }
 
-void TestSyncUserSettings::ClearSyncRequested() {
-  SyncService::DisableReasonSet disable_reasons = service_->GetDisableReasons();
-  disable_reasons.Put(SyncService::DISABLE_REASON_USER_CHOICE);
-  service_->SetDisableReasons(disable_reasons);
-}
-
 bool TestSyncUserSettings::IsFirstSetupComplete() const {
   return first_setup_complete_;
 }
diff --git a/components/sync/test/test_sync_user_settings.h b/components/sync/test/test_sync_user_settings.h
index 990d54a..abd5c1e 100644
--- a/components/sync/test/test_sync_user_settings.h
+++ b/components/sync/test/test_sync_user_settings.h
@@ -25,7 +25,6 @@
   explicit TestSyncUserSettings(TestSyncService* service);
   ~TestSyncUserSettings() override;
 
-  bool IsSyncRequested() const override;
   void SetSyncRequested() override;
 
   bool IsFirstSetupComplete() const override;
@@ -70,10 +69,6 @@
   void SetDecryptionNigoriKey(std::unique_ptr<Nigori> nigori) override;
   std::unique_ptr<Nigori> GetDecryptionNigoriKey() const override;
 
-  // TODO(crbug.com/1219990): Remove or rename this function since there is no
-  // UI for the user to achieve this, with the exception of ChromeOS for the
-  // case where the user clears sync data via dashboard.
-  void ClearSyncRequested();
   void SetFirstSetupComplete();
   void ClearFirstSetupComplete();
   void SetCustomPassphraseAllowed(bool allowed);
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 5086de0..f103049c 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1516,6 +1516,8 @@
     "preloading/prerender/prerender_new_tab_handle.h",
     "preloading/prerender/prerender_subframe_navigation_throttle.cc",
     "preloading/prerender/prerender_subframe_navigation_throttle.h",
+    "preloading/prerender/prerender_trigger_type_impl.cc",
+    "preloading/prerender/prerender_trigger_type_impl.h",
     "preloading/prerenderer.h",
     "preloading/prerenderer_impl.cc",
     "preloading/prerenderer_impl.h",
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index 9206d99..18584d66 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -291,8 +291,9 @@
     prerender_helper_->WaitForRequest(url, count);
   }
 
-  int AddPrerender(const GURL& prerendering_url) {
-    return prerender_helper_->AddPrerender(prerendering_url);
+  int AddPrerender(const GURL& prerendering_url,
+                   int32_t world_id = ISOLATED_WORLD_ID_GLOBAL) {
+    return prerender_helper_->AddPrerender(prerendering_url, world_id);
   }
 
   void AddPrerenderAsync(const GURL& prerendering_url) {
@@ -505,11 +506,10 @@
     EXPECT_TRUE(WaitForLoadStop(web_contents()));
   }
 
-  void ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus status) {
+  void ExpectFinalStatus(const std::string& final_status_name,
+                         PrerenderFinalStatus status) {
     // Check FinalStatus in UMA.
-    histogram_tester().ExpectUniqueSample(
-        "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
-        status, 1);
+    histogram_tester().ExpectUniqueSample(final_status_name, status, 1);
 
     // Check all entries in UKM to make sure that the recorded FinalStatus is
     // equal to `status`. At least one entry should exist.
@@ -529,13 +529,21 @@
     EXPECT_TRUE(final_status_entry_found);
   }
 
-  void ExpectFinalStatusForEmbedder(PrerenderFinalStatus status) {
-    // Check FinalStatus in UMA.
-    histogram_tester().ExpectUniqueSample(
-        "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
-        "EmbedderSuffixForTest",
-        status, 1);
+  void ExpectFinalStatusForSpeculationRule(PrerenderFinalStatus status) {
+    ExpectFinalStatus(
+        "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule",
+        status);
+  }
 
+  void ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(
+      PrerenderFinalStatus status) {
+    ExpectFinalStatus(
+        "Prerender.Experimental.PrerenderHostFinalStatus."
+        "SpeculationRuleFromIsolatedWorld",
+        status);
+  }
+
+  void ExpectFinalStatusForEmbedder(PrerenderFinalStatus status) {
     // UKM can be recorded in an initiator page and an activated page. Embedder
     // triggers don't have an initiator page, so UKM is not recorded anywhere
     // when prerendering is canceled.
@@ -543,22 +551,10 @@
       return;
     }
 
-    // Check all entries in UKM to make sure that the recorded FinalStatus is
-    // equal to `status`. At least one entry should exist.
-    bool final_status_entry_found = false;
-    const auto entries = ukm_recorder_->GetEntriesByName(
-        ukm::builders::PrerenderPageLoad::kEntryName);
-    for (const auto* entry : entries) {
-      if (ukm_recorder_->EntryHasMetric(
-              entry, ukm::builders::PrerenderPageLoad::kFinalStatusName)) {
-        final_status_entry_found = true;
-        ukm_recorder_->ExpectEntryMetric(
-            entry, ukm::builders::PrerenderPageLoad::kFinalStatusName,
-            static_cast<int>(status));
-      }
-    }
-
-    EXPECT_TRUE(final_status_entry_found);
+    ExpectFinalStatus(
+        "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_"
+        "EmbedderSuffixForTest",
+        status);
   }
 
   const base::HistogramTester& histogram_tester() { return histogram_tester_; }
@@ -2303,6 +2299,9 @@
       case PrerenderTriggerType::kSpeculationRule:
         host_id = AddPrerender(kPrerenderingUrl);
         break;
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+        host_id = AddPrerender(kPrerenderingUrl, /*world_id=*/1);
+        break;
       case PrerenderTriggerType::kEmbedder:
         prerender_handle = AddEmbedderTriggeredPrerender(kPrerenderingUrl);
         host_id = static_cast<PrerenderHandleImpl*>(prerender_handle.get())
@@ -2338,6 +2337,7 @@
         // Activation should succeed.
         switch (trigger_type) {
           case PrerenderTriggerType::kSpeculationRule:
+          case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
             NavigatePrimaryPage(kPrerenderingUrl);
             break;
           case PrerenderTriggerType::kEmbedder:
@@ -2363,6 +2363,9 @@
       case PrerenderTriggerType::kSpeculationRule:
         ExpectFinalStatusForSpeculationRule(expected_status);
         break;
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+        ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(expected_status);
+        break;
       case PrerenderTriggerType::kEmbedder:
         ExpectFinalStatusForEmbedder(expected_status);
         break;
@@ -2401,6 +2404,9 @@
       case PrerenderTriggerType::kSpeculationRule:
         host_id = AddPrerender(kPrerenderingUrl);
         break;
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+        host_id = AddPrerender(kPrerenderingUrl, /*world_id=*/1);
+        break;
       case PrerenderTriggerType::kEmbedder:
         prerender_handle = AddEmbedderTriggeredPrerender(kPrerenderingUrl);
         host_id = static_cast<PrerenderHandleImpl*>(prerender_handle.get())
@@ -2424,6 +2430,7 @@
         // Activation should succeed.
         switch (trigger_type) {
           case PrerenderTriggerType::kSpeculationRule:
+          case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
             NavigatePrimaryPage(kPrerenderingUrl);
             break;
           case PrerenderTriggerType::kEmbedder:
@@ -2449,6 +2456,9 @@
       case PrerenderTriggerType::kSpeculationRule:
         ExpectFinalStatusForSpeculationRule(expected_status);
         break;
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+        ExpectFinalStatusForSpeculationRuleFromIsolatedWorld(expected_status);
+        break;
       case PrerenderTriggerType::kEmbedder:
         ExpectFinalStatusForEmbedder(expected_status);
         break;
@@ -2495,11 +2505,14 @@
     All,
     PrerenderMainFrameNavigationBrowserTest,
     testing::Values(PrerenderTriggerType::kSpeculationRule,
+                    PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld,
                     PrerenderTriggerType::kEmbedder),
     [](const testing::TestParamInfo<PrerenderTriggerType>& info) {
       switch (info.param) {
         case PrerenderTriggerType::kSpeculationRule:
           return "SpeculationRule";
+        case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+          return "SpeculationRuleFromIsolatedWorld";
         case PrerenderTriggerType::kEmbedder:
           return "Embedder";
       }
diff --git a/content/browser/preloading/prerender/prerender_host_registry.cc b/content/browser/preloading/prerender/prerender_host_registry.cc
index 8cd03ae..d7788e8 100644
--- a/content/browser/preloading/prerender/prerender_host_registry.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry.cc
@@ -24,6 +24,7 @@
 #include "content/browser/preloading/prerender/prerender_metrics.h"
 #include "content/browser/preloading/prerender/prerender_navigation_utils.h"
 #include "content/browser/preloading/prerender/prerender_new_tab_handle.h"
+#include "content/browser/preloading/prerender/prerender_trigger_type_impl.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -448,6 +449,7 @@
           blink::features::kPrerender2SequentialPrerendering)) {
     switch (attributes.trigger_type) {
       case PrerenderTriggerType::kSpeculationRule:
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
         pending_prerenders_.push_back(frame_tree_node_id);
         // Start the initial prerendering navigation of the pending request in
         // the head of the queue if there's no running prerender.
@@ -485,7 +487,7 @@
 int PrerenderHostRegistry::CreateAndStartHostForNewTab(
     const PrerenderAttributes& attributes) {
   CHECK(base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab));
-  CHECK_EQ(attributes.trigger_type, PrerenderTriggerType::kSpeculationRule);
+  CHECK(IsSpeculationRuleType(attributes.trigger_type));
   std::string recorded_url =
       attributes.initiator_origin.has_value()
           ? attributes.initiator_origin.value().GetURL().spec()
@@ -564,6 +566,7 @@
   switch (prerender_host_by_frame_tree_node_id_[frame_tree_node_id]
               ->trigger_type()) {
     case PrerenderTriggerType::kSpeculationRule:
+    case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
       DestroyWhenUsingExcessiveMemory(frame_tree_node_id);
       break;
     case PrerenderTriggerType::kEmbedder:
@@ -578,6 +581,7 @@
     switch (prerender_host_by_frame_tree_node_id_[frame_tree_node_id]
                 ->trigger_type()) {
       case PrerenderTriggerType::kSpeculationRule:
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
         running_prerender_host_id_ = frame_tree_node_id;
         break;
       case PrerenderTriggerType::kEmbedder:
@@ -668,31 +672,27 @@
   return !cancelled_ids.empty();
 }
 
-void PrerenderHostRegistry::CancelHostsForTrigger(
-    PrerenderTriggerType trigger_type,
+void PrerenderHostRegistry::CancelHostsForTriggers(
+    std::vector<PrerenderTriggerType> trigger_types,
     const PrerenderCancellationReason& reason) {
   TRACE_EVENT1("navigation", "PrerenderHostRegistry::CancelHostsForTrigger",
-               "trigger_type", trigger_type);
+               "trigger_type", trigger_types[0]);
 
   std::vector<int> ids_to_be_deleted;
 
   for (auto& iter : prerender_host_by_frame_tree_node_id_) {
-    if (iter.second->trigger_type() == trigger_type) {
+    if (base::Contains(trigger_types, iter.second->trigger_type())) {
       ids_to_be_deleted.push_back(iter.first);
     }
   }
-
   if (base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab)) {
-    switch (trigger_type) {
-      case PrerenderTriggerType::kSpeculationRule:
-        for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
-          ids_to_be_deleted.push_back(iter.first);
-        }
-        break;
-      case PrerenderTriggerType::kEmbedder:
-        // Prerendering into a new tab can be triggered by speculation
-        // rules only.
-        break;
+    for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
+      if (base::Contains(trigger_types, iter.second->trigger_type())) {
+        // Prerendering into a new tab can be triggered by speculation rules
+        // only.
+        CHECK(IsSpeculationRuleType(iter.second->trigger_type()));
+        ids_to_be_deleted.push_back(iter.first);
+      }
     }
   } else {
     CHECK(prerender_new_tab_handle_by_frame_tree_node_id_.empty());
@@ -1116,17 +1116,21 @@
     // amount of time. The timeout differs depending on the trigger type.
     timeout_timer_for_embedder_.Start(
         FROM_HERE, kTimeToLiveInBackgroundForEmbedder,
-        base::BindOnce(&PrerenderHostRegistry::CancelHostsForTrigger,
-                       base::Unretained(this), PrerenderTriggerType::kEmbedder,
+        base::BindOnce(&PrerenderHostRegistry::CancelHostsForTriggers,
+                       base::Unretained(this),
+                       std::vector({PrerenderTriggerType::kEmbedder}),
                        PrerenderCancellationReason(
                            PrerenderFinalStatus::kTimeoutBackgrounded)));
     timeout_timer_for_speculation_rules_.Start(
         FROM_HERE, kTimeToLiveInBackgroundForSpeculationRules,
-        base::BindOnce(&PrerenderHostRegistry::CancelHostsForTrigger,
-                       base::Unretained(this),
-                       PrerenderTriggerType::kSpeculationRule,
-                       PrerenderCancellationReason(
-                           PrerenderFinalStatus::kTimeoutBackgrounded)));
+        base::BindOnce(
+            &PrerenderHostRegistry::CancelHostsForTriggers,
+            base::Unretained(this),
+            std::vector(
+                {PrerenderTriggerType::kSpeculationRule,
+                 PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld}),
+            PrerenderCancellationReason(
+                PrerenderFinalStatus::kTimeoutBackgrounded)));
   } else {
     // Stop the timer when a prerendered page gets visible to users.
     timeout_timer_for_embedder_.Stop();
@@ -1331,6 +1335,7 @@
 
   switch (trigger_type) {
     case PrerenderTriggerType::kSpeculationRule:
+    case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
       // The number of prerenders triggered by speculation rules is limited to a
       // Finch config param.
       return trigger_type_count <
diff --git a/content/browser/preloading/prerender/prerender_host_registry.h b/content/browser/preloading/prerender/prerender_host_registry.h
index 1c1000b..e61d809 100644
--- a/content/browser/preloading/prerender/prerender_host_registry.h
+++ b/content/browser/preloading/prerender/prerender_host_registry.h
@@ -140,10 +140,6 @@
   std::set<int> CancelHosts(const std::vector<int>& frame_tree_node_ids,
                             const PrerenderCancellationReason& reason);
 
-  // Cancels the existing hosts that were triggered by `trigger_type`.
-  void CancelHostsForTrigger(PrerenderTriggerType trigger_type,
-                             const PrerenderCancellationReason& reason);
-
   // Applies CancelHost for all existing PrerenderHost.
   void CancelAllHosts(PrerenderFinalStatus final_status);
 
@@ -266,6 +262,10 @@
   // cancelled.
   int StartPrerendering(int frame_tree_node_id);
 
+  // Cancels the existing hosts that were triggered by `trigger_types`.
+  void CancelHostsForTriggers(std::vector<PrerenderTriggerType> trigger_types,
+                              const PrerenderCancellationReason& reason);
+
   // Returns whether a certain type of PrerenderTriggerType is allowed to be
   // added to PrerenderHostRegistry according to the limit of the given
   // PrerenderTriggerType.
diff --git a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
index 5360504f..fa8dc02 100644
--- a/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry_unittest.cc
@@ -198,6 +198,7 @@
       RenderFrameHostImpl* rfh) {
     switch (trigger_type) {
       case PrerenderTriggerType::kSpeculationRule:
+      case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
         return PrerenderAttributes(
             url, trigger_type, embedder_histogram_suffix, Referrer(),
             rfh->GetLastCommittedOrigin(), rfh->GetProcess()->GetID(),
diff --git a/content/browser/preloading/prerender/prerender_metrics.cc b/content/browser/preloading/prerender/prerender_metrics.cc
index 872cc930..b9d0c88 100644
--- a/content/browser/preloading/prerender/prerender_metrics.cc
+++ b/content/browser/preloading/prerender/prerender_metrics.cc
@@ -9,6 +9,7 @@
 #include "base/metrics/metrics_hashes.h"
 #include "base/strings/string_util.h"
 #include "content/browser/devtools/devtools_instrumentation.h"
+#include "content/browser/preloading/prerender/prerender_trigger_type_impl.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/public/browser/prerender_trigger_type.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -61,6 +62,10 @@
     case PrerenderTriggerType::kSpeculationRule:
       CHECK(embedder_suffix.empty());
       return std::string(histogram_base_name) + ".SpeculationRule";
+    case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+      CHECK(embedder_suffix.empty());
+      return std::string(histogram_base_name) +
+             ".SpeculationRuleFromIsolatedWorld";
     case PrerenderTriggerType::kEmbedder:
       CHECK(!embedder_suffix.empty());
       return std::string(histogram_base_name) + ".Embedder_" + embedder_suffix;
@@ -215,7 +220,7 @@
 
   if (attributes.initiator_ukm_id != ukm::kInvalidSourceId) {
     // `initiator_ukm_id` must be valid for the speculation rules.
-    CHECK_EQ(attributes.trigger_type, PrerenderTriggerType::kSpeculationRule);
+    CHECK(IsSpeculationRuleType(attributes.trigger_type));
     ukm::builders::PrerenderPageLoad(attributes.initiator_ukm_id)
         .SetFinalStatus(static_cast<int>(cancellation_reason.final_status()))
         .Record(ukm::UkmRecorder::Get());
@@ -247,7 +252,7 @@
                                 attributes.embedder_histogram_suffix);
   if (attributes.initiator_ukm_id != ukm::kInvalidSourceId) {
     // `initiator_ukm_id` must be valid only for the speculation rules.
-    CHECK_EQ(attributes.trigger_type, PrerenderTriggerType::kSpeculationRule);
+    CHECK(IsSpeculationRuleType(attributes.trigger_type));
     ukm::builders::PrerenderPageLoad(attributes.initiator_ukm_id)
         .SetFinalStatus(static_cast<int>(PrerenderFinalStatus::kActivated))
         .Record(ukm::UkmRecorder::Get());
diff --git a/content/browser/preloading/prerender/prerender_new_tab_handle.h b/content/browser/preloading/prerender/prerender_new_tab_handle.h
index ceae2348..3b7b589 100644
--- a/content/browser/preloading/prerender/prerender_new_tab_handle.h
+++ b/content/browser/preloading/prerender/prerender_new_tab_handle.h
@@ -56,6 +56,9 @@
   // Returns PrerenderHost that `web_contents_` is hosting.
   PrerenderHost* GetPrerenderHostForTesting();
 
+  // Returns PrerenderTriggerType.
+  PrerenderTriggerType trigger_type() const { return attributes_.trigger_type; }
+
  private:
   PrerenderHostRegistry& GetPrerenderHostRegistry();
 
diff --git a/content/browser/preloading/prerender/prerender_trigger_type_impl.cc b/content/browser/preloading/prerender/prerender_trigger_type_impl.cc
new file mode 100644
index 0000000..b041ac3
--- /dev/null
+++ b/content/browser/preloading/prerender/prerender_trigger_type_impl.cc
@@ -0,0 +1,20 @@
+// 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 "content/browser/preloading/prerender/prerender_trigger_type_impl.h"
+
+namespace content {
+
+bool IsSpeculationRuleType(PrerenderTriggerType type) {
+  switch (type) {
+    case PrerenderTriggerType::kSpeculationRule:
+      [[fallthrough]];
+    case PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+      return true;
+    case PrerenderTriggerType::kEmbedder:
+      return false;
+  }
+}
+
+}  // namespace content
diff --git a/content/browser/preloading/prerender/prerender_trigger_type_impl.h b/content/browser/preloading/prerender/prerender_trigger_type_impl.h
new file mode 100644
index 0000000..69d62b2
--- /dev/null
+++ b/content/browser/preloading/prerender/prerender_trigger_type_impl.h
@@ -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.
+
+#ifndef CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_TRIGGER_TYPE_IMPL_H_
+#define CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_TRIGGER_TYPE_IMPL_H_
+
+#include "content/public/browser/prerender_trigger_type.h"
+
+namespace content {
+
+// Checks if the type is kSpeculationRule*. Recommends to use this function to
+// keep the code robust against adding more trigger types in the future.
+bool IsSpeculationRuleType(PrerenderTriggerType type);
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PRELOADING_PRERENDER_PRERENDER_TRIGGER_TYPE_IMPL_H_
diff --git a/content/browser/preloading/prerenderer_impl.cc b/content/browser/preloading/prerenderer_impl.cc
index 673581d..8d0f3b3 100644
--- a/content/browser/preloading/prerenderer_impl.cc
+++ b/content/browser/preloading/prerenderer_impl.cc
@@ -214,7 +214,11 @@
 
   Referrer referrer(*(candidate->referrer));
   PrerenderAttributes attributes(
-      candidate->url, PrerenderTriggerType::kSpeculationRule,
+      candidate->url,
+      candidate->injection_world !=
+              blink::mojom::SpeculationInjectionWorld::kIsolated
+          ? PrerenderTriggerType::kSpeculationRule
+          : PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld,
       /*embedder_histogram_suffix=*/"", referrer, rfhi.GetLastCommittedOrigin(),
       rfhi.GetProcess()->GetID(), web_contents->GetWeakPtr(),
       rfhi.GetFrameToken(), rfhi.GetFrameTreeNodeId(),
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
index 7e626cf7..7079e8b 100644
--- a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
@@ -287,6 +287,8 @@
 
   if (timeout_handler_)
     timeout_handler_->StartIfNecessary(*touch);
+  touch->event.GetModifiableEventLatencyMetadata().dispatched_to_renderer =
+      base::TimeTicks::Now();
   if (wait_for_ack)
     outstanding_touches_.insert(*touch);
   client_->SendTouchEventImmediately(*touch);
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
index e706e1e5..f1aa2eb4 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker.cc
@@ -94,7 +94,8 @@
 
 void RenderWidgetHostLatencyTracker::OnInputEvent(
     const blink::WebInputEvent& event,
-    LatencyInfo* latency) {
+    LatencyInfo* latency,
+    ui::EventLatencyMetadata* event_latency_metadata) {
   DCHECK(latency);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -135,9 +136,12 @@
         ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, timestamp_original);
   }
 
+  base::TimeTicks begin_rwh_timestamp = base::TimeTicks::Now();
   latency->AddLatencyNumberWithTraceName(
       ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
-      GetTraceNameFromType(event.GetType()));
+      GetTraceNameFromType(event.GetType()), begin_rwh_timestamp);
+  event_latency_metadata->arrived_in_browser_main_timestamp =
+      begin_rwh_timestamp;
 
   if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
     has_seen_first_gesture_scroll_update_ = false;
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker.h b/content/browser/renderer_host/input/render_widget_host_latency_tracker.h
index a6acd1f..c1cca70 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker.h
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker.h
@@ -35,7 +35,8 @@
   // Called when an event is received by the RenderWidgetHost, prior to
   // that event being forwarded to the renderer (via the InputRouter).
   void OnInputEvent(const blink::WebInputEvent& event,
-                    ui::LatencyInfo* latency);
+                    ui::LatencyInfo* latency,
+                    ui::EventLatencyMetadata* event_latency_metadata);
 
   // Populates the LatencyInfo with relevant entries for latency tracking, also
   // terminating latency tracking for events that did not trigger rendering and
diff --git a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
index 38c9e17..39b431f 100644
--- a/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
+++ b/content/browser/renderer_host/input/render_widget_host_latency_tracker_unittest.cc
@@ -222,16 +222,22 @@
           blink::WebMouseWheelEvent::kPhaseChanged);
       base::TimeTicks now = base::TimeTicks::Now();
       wheel.SetTimeStamp(now);
+      ui::EventLatencyMetadata event_latency_metadata;
       ui::LatencyInfo wheel_latency(ui::SourceEventType::WHEEL);
       wheel_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, now);
       AddFakeComponentsWithTimeStamp(*tracker(), &wheel_latency, now);
       AddRenderingScheduledComponent(&wheel_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(wheel, &wheel_latency);
+      tracker()->OnInputEvent(wheel, &wheel_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(wheel_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(wheel_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           wheel, &wheel_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -287,16 +293,22 @@
           blink::WebMouseWheelEvent::kPhaseChanged);
       base::TimeTicks now = base::TimeTicks::Now();
       wheel.SetTimeStamp(now);
+      ui::EventLatencyMetadata event_latency_metadata;
       ui::LatencyInfo wheel_latency(ui::SourceEventType::WHEEL);
       wheel_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, now);
       AddFakeComponentsWithTimeStamp(*tracker(), &wheel_latency, now);
       AddRenderingScheduledComponent(&wheel_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(wheel, &wheel_latency);
+      tracker()->OnInputEvent(wheel, &wheel_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(wheel_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(wheel_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           wheel, &wheel_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -350,13 +362,19 @@
       base::TimeTicks now = base::TimeTicks::Now();
       scroll.SetTimeStamp(now);
       ui::LatencyInfo scroll_latency(ui::SourceEventType::INERTIAL);
+      ui::EventLatencyMetadata event_latency_metadata;
       AddFakeComponentsWithTimeStamp(*tracker(), &scroll_latency, now);
       AddRenderingScheduledComponent(&scroll_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(scroll, &scroll_latency);
+      tracker()->OnInputEvent(scroll, &scroll_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(scroll_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(scroll_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           scroll, &scroll_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -396,13 +414,19 @@
       base::TimeTicks now = base::TimeTicks::Now();
       scroll.SetTimeStamp(now);
       ui::LatencyInfo scroll_latency;
+      ui::EventLatencyMetadata event_latency_metadata;
       AddFakeComponentsWithTimeStamp(*tracker(), &scroll_latency, now);
       AddRenderingScheduledComponent(&scroll_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(scroll, &scroll_latency);
+      tracker()->OnInputEvent(scroll, &scroll_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(scroll_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(scroll_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           scroll, &scroll_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -412,17 +436,23 @@
       blink::SyntheticWebTouchEvent touch;
       touch.PressPoint(0, 0);
       touch.PressPoint(1, 1);
+      ui::EventLatencyMetadata event_latency_metadata;
       ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
       base::TimeTicks now = base::TimeTicks::Now();
       touch_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, now);
       AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, now);
       AddRenderingScheduledComponent(&touch_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(touch, &touch_latency);
+      tracker()->OnInputEvent(touch, &touch_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(touch_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(touch_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           touch, &touch_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -476,9 +506,10 @@
       base::TimeTicks now = base::TimeTicks::Now();
       scroll.SetTimeStamp(now);
       ui::LatencyInfo scroll_latency;
+      ui::EventLatencyMetadata event_latency_metadata;
       AddFakeComponentsWithTimeStamp(*tracker(), &scroll_latency, now);
       AddRenderingScheduledComponent(&scroll_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(scroll, &scroll_latency);
+      tracker()->OnInputEvent(scroll, &scroll_latency, &event_latency_metadata);
       EXPECT_TRUE(scroll_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
       EXPECT_TRUE(scroll_latency.FindLatency(
@@ -493,16 +524,22 @@
       touch.PressPoint(0, 0);
       touch.PressPoint(1, 1);
       ui::LatencyInfo touch_latency(ui::SourceEventType::TOUCH);
+      ui::EventLatencyMetadata event_latency_metadata;
       base::TimeTicks now = base::TimeTicks::Now();
       touch_latency.AddLatencyNumberWithTimestamp(
           ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, now);
       AddFakeComponentsWithTimeStamp(*tracker(), &touch_latency, now);
       AddRenderingScheduledComponent(&touch_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(touch, &touch_latency);
+      tracker()->OnInputEvent(touch, &touch_latency, &event_latency_metadata);
+      base::TimeTicks begin_rwh_timestamp;
       EXPECT_TRUE(touch_latency.FindLatency(
-          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+          ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
       EXPECT_TRUE(touch_latency.FindLatency(
           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+      EXPECT_FALSE(
+          event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+      EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                begin_rwh_timestamp);
       tracker()->OnInputEventAck(
           touch, &touch_latency,
           blink::mojom::InputEventResultState::kNotConsumed);
@@ -558,14 +595,21 @@
       const bool on_main[] = {true, false};
       for (bool on_main_thread : on_main) {
         ui::LatencyInfo scrollbar_latency(ui::SourceEventType::SCROLLBAR);
+        ui::EventLatencyMetadata event_latency_metadata;
         AddFakeComponentsWithTimeStamp(*tracker(), &scrollbar_latency, now);
         scrollbar_latency.AddLatencyNumberWithTimestamp(component, now);
         AddRenderingScheduledComponent(&scrollbar_latency, on_main_thread, now);
-        tracker()->OnInputEvent(mouse_move, &scrollbar_latency);
+        tracker()->OnInputEvent(mouse_move, &scrollbar_latency,
+                                &event_latency_metadata);
+        base::TimeTicks begin_rwh_timestamp;
         EXPECT_TRUE(scrollbar_latency.FindLatency(
-            ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+            ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
         EXPECT_TRUE(scrollbar_latency.FindLatency(
             ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, nullptr));
+        EXPECT_FALSE(
+            event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+        EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+                  begin_rwh_timestamp);
         tracker()->OnInputEventAck(
             mouse_move, &scrollbar_latency,
             blink::mojom::InputEventResultState::kNotConsumed);
@@ -594,13 +638,14 @@
       base::TimeTicks now = base::TimeTicks::Now();
       scroll.SetTimeStamp(now);
       ui::LatencyInfo scroll_latency;
+      ui::EventLatencyMetadata event_latency_metadata;
       scroll_latency.set_source_event_type(
           source_device == blink::WebGestureDevice::kTouchscreen
               ? ui::SourceEventType::TOUCH
               : ui::SourceEventType::WHEEL);
       AddFakeComponentsWithTimeStamp(*tracker(), &scroll_latency, now);
       AddRenderingScheduledComponent(&scroll_latency, rendering_on_main, now);
-      tracker()->OnInputEvent(scroll, &scroll_latency);
+      tracker()->OnInputEvent(scroll, &scroll_latency, &event_latency_metadata);
       tracker()->OnInputEventAck(
           scroll, &scroll_latency,
           blink::mojom::InputEventResultState::kNoConsumerExists);
@@ -613,11 +658,18 @@
   auto scroll_begin = blink::SyntheticWebGestureEventBuilder::BuildScrollBegin(
       5, -5, blink::WebGestureDevice::kTouchscreen);
   ui::LatencyInfo scroll_latency;
+  ui::EventLatencyMetadata event_latency_metadata;
   scroll_latency.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
-  tracker()->OnInputEvent(scroll_begin, &scroll_latency);
+  tracker()->OnInputEvent(scroll_begin, &scroll_latency,
+                          &event_latency_metadata);
+  base::TimeTicks begin_rwh_timestamp;
   EXPECT_TRUE(scroll_latency.FindLatency(
-      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
   EXPECT_EQ(2U, scroll_latency.latency_components().size());
+  EXPECT_FALSE(
+      event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+  EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+            begin_rwh_timestamp);
 
   // The first GestureScrollUpdate should be provided with
   // INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT.
@@ -625,15 +677,22 @@
       blink::SyntheticWebGestureEventBuilder::BuildScrollUpdate(
           5.f, -5.f, 0, blink::WebGestureDevice::kTouchscreen);
   scroll_latency = ui::LatencyInfo();
+  event_latency_metadata = ui::EventLatencyMetadata();
   scroll_latency.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
-  tracker()->OnInputEvent(first_scroll_update, &scroll_latency);
+  tracker()->OnInputEvent(first_scroll_update, &scroll_latency,
+                          &event_latency_metadata);
+  begin_rwh_timestamp = base::TimeTicks();
   EXPECT_TRUE(scroll_latency.FindLatency(
-      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
   EXPECT_TRUE(scroll_latency.FindLatency(
       ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, nullptr));
   EXPECT_FALSE(scroll_latency.FindLatency(
       ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, nullptr));
   EXPECT_EQ(3U, scroll_latency.latency_components().size());
+  EXPECT_FALSE(
+      event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+  EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+            begin_rwh_timestamp);
 
   // Subsequent GestureScrollUpdates should be provided with
   // INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT.
@@ -641,15 +700,22 @@
       blink::SyntheticWebGestureEventBuilder::BuildScrollUpdate(
           -5.f, 5.f, 0, blink::WebGestureDevice::kTouchscreen);
   scroll_latency = ui::LatencyInfo();
+  event_latency_metadata = ui::EventLatencyMetadata();
   scroll_latency.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
-  tracker()->OnInputEvent(scroll_update, &scroll_latency);
+  tracker()->OnInputEvent(scroll_update, &scroll_latency,
+                          &event_latency_metadata);
+  begin_rwh_timestamp = base::TimeTicks();
   EXPECT_TRUE(scroll_latency.FindLatency(
-      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr));
+      ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, &begin_rwh_timestamp));
   EXPECT_FALSE(scroll_latency.FindLatency(
       ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, nullptr));
   EXPECT_TRUE(scroll_latency.FindLatency(
       ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, nullptr));
   EXPECT_EQ(3U, scroll_latency.latency_components().size());
+  EXPECT_FALSE(
+      event_latency_metadata.arrived_in_browser_main_timestamp.is_null());
+  EXPECT_EQ(event_latency_metadata.arrived_in_browser_main_timestamp,
+            begin_rwh_timestamp);
 }
 
 TEST_F(RenderWidgetHostLatencyTrackerTest, KeyEndToEndLatency) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index f6c7102..5e8403e 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1497,7 +1497,9 @@
   }
 
   MouseEventWithLatencyInfo mouse_with_latency(mouse_event, latency);
-  DispatchInputEventWithLatencyInfo(mouse_event, &mouse_with_latency.latency);
+  DispatchInputEventWithLatencyInfo(
+      mouse_with_latency.event, &mouse_with_latency.latency,
+      &mouse_with_latency.event.GetModifiableEventLatencyMetadata());
   input_router_->SendMouseEvent(
       mouse_with_latency, base::BindOnce(&RenderWidgetHostImpl::OnMouseEventAck,
                                          weak_factory_.GetWeakPtr()));
@@ -1523,7 +1525,9 @@
     return;
 
   MouseWheelEventWithLatencyInfo wheel_with_latency(wheel_event, latency);
-  DispatchInputEventWithLatencyInfo(wheel_event, &wheel_with_latency.latency);
+  DispatchInputEventWithLatencyInfo(
+      wheel_with_latency.event, &wheel_with_latency.latency,
+      &wheel_with_latency.event.GetModifiableEventLatencyMetadata());
   input_router_->SendWheelEvent(wheel_with_latency);
 }
 
@@ -1643,8 +1647,9 @@
     return;
 
   GestureEventWithLatencyInfo gesture_with_latency(gesture_event, latency);
-  DispatchInputEventWithLatencyInfo(gesture_event,
-                                    &gesture_with_latency.latency);
+  DispatchInputEventWithLatencyInfo(
+      gesture_with_latency.event, &gesture_with_latency.latency,
+      &gesture_with_latency.event.GetModifiableEventLatencyMetadata());
   input_router_->SendGestureEvent(gesture_with_latency);
 }
 
@@ -1662,7 +1667,9 @@
   // ignored if appropriate in FilterInputEvent().
 
   TouchEventWithLatencyInfo touch_with_latency(touch_event, latency);
-  DispatchInputEventWithLatencyInfo(touch_event, &touch_with_latency.latency);
+  DispatchInputEventWithLatencyInfo(
+      touch_with_latency.event, &touch_with_latency.latency,
+      &touch_with_latency.event.GetModifiableEventLatencyMetadata());
   input_router_->SendTouchEvent(touch_with_latency);
 }
 
@@ -1771,7 +1778,9 @@
   NativeWebKeyboardEventWithLatencyInfo key_event_with_latency(key_event,
                                                                latency);
   key_event_with_latency.event.is_browser_shortcut = is_shortcut;
-  DispatchInputEventWithLatencyInfo(key_event, &key_event_with_latency.latency);
+  DispatchInputEventWithLatencyInfo(
+      key_event_with_latency.event, &key_event_with_latency.latency,
+      &key_event_with_latency.event.GetModifiableEventLatencyMetadata());
   // TODO(foolip): |InputRouter::SendKeyboardEvent()| may filter events, in
   // which the commands will be treated as belonging to the next key event.
   // WidgetInputHandler::SetEditCommandsForNextKeyEvent should only be sent if
@@ -3245,8 +3254,9 @@
 
 void RenderWidgetHostImpl::DispatchInputEventWithLatencyInfo(
     const blink::WebInputEvent& event,
-    ui::LatencyInfo* latency) {
-  latency_tracker_.OnInputEvent(event, latency);
+    ui::LatencyInfo* latency,
+    ui::EventLatencyMetadata* event_latency_metadata) {
+  latency_tracker_.OnInputEvent(event, latency, event_latency_metadata);
   AddPendingUserActivation(event);
   for (auto& observer : input_event_observers_)
     observer.OnInputEvent(event);
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index dd25d63..76a2bd4 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -1079,8 +1079,10 @@
   void OnInvalidInputEventSource() override;
 
   // Dispatch input events with latency information
-  void DispatchInputEventWithLatencyInfo(const blink::WebInputEvent& event,
-                                         ui::LatencyInfo* latency);
+  void DispatchInputEventWithLatencyInfo(
+      const blink::WebInputEvent& event,
+      ui::LatencyInfo* latency,
+      ui::EventLatencyMetadata* event_latency_metadata);
 
   void WindowSnapshotReachedScreen(int snapshot_id);
 
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 68d74a9..539a8b0 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -2351,8 +2351,10 @@
   NativeWebKeyboardEvent native_event(WebInputEvent::Type::kChar, 0,
                                       GetNextSimulatedEventTime());
   ui::LatencyInfo latency_info = ui::LatencyInfo();
+  ui::EventLatencyMetadata event_latency_metadata;
   EXPECT_CALL(observer, OnInputEvent(_)).Times(1);
-  host_->DispatchInputEventWithLatencyInfo(native_event, &latency_info);
+  host_->DispatchInputEventWithLatencyInfo(native_event, &latency_info,
+                                           &event_latency_metadata);
 
   // Remove InputEventObserver.
   host_->RemoveInputEventObserver(&observer);
@@ -2360,7 +2362,9 @@
   // Confirm InputEventObserver is removed.
   EXPECT_CALL(observer, OnInputEvent(_)).Times(0);
   latency_info = ui::LatencyInfo();
-  host_->DispatchInputEventWithLatencyInfo(native_event, &latency_info);
+  event_latency_metadata = ui::EventLatencyMetadata();
+  host_->DispatchInputEventWithLatencyInfo(native_event, &latency_info,
+                                           &event_latency_metadata);
 }
 
 #if BUILDFLAG(IS_ANDROID)
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 178efd7..5adf34a 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -2112,9 +2112,20 @@
     blink::mojom::InputEventResultState ack_result) {
   const bool event_consumed =
       ack_result == blink::mojom::InputEventResultState::kConsumed;
+  // |is_source_touch_event_set_non_blocking| defines a blocking behaviour of
+  // the future inputs.
+  const bool is_source_touch_event_set_non_blocking =
+      InputEventResultStateIsSetNonBlocking(ack_result);
+  // |was_touch_blocked| indicates whether the current event was dispatched
+  // blocking to the Renderer.
+  const bool was_touch_blocked =
+      ui::WebInputEventTraits::ShouldBlockEventStream(touch.event);
   gesture_provider_.OnTouchEventAck(
       touch.event.unique_touch_event_id, event_consumed,
-      InputEventResultStateIsSetNonBlocking(ack_result));
+      is_source_touch_event_set_non_blocking,
+      was_touch_blocked
+          ? absl::make_optional(touch.event.GetEventLatencyMetadata())
+          : absl::nullopt);
   if (touch.event.touch_start_or_first_touch_move && event_consumed &&
       host()->delegate() && host()->delegate()->GetInputEventRouter()) {
     host()
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index a92d133..a164080 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -628,6 +628,7 @@
     "//ui/color:mojom",
     "//ui/display/mojom",
     "//ui/events/mojom",
+    "//ui/events/mojom:event_latency_metadata_mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/image/mojom",
     "//ui/gfx/mojom",
diff --git a/content/public/browser/prerender_trigger_type.h b/content/public/browser/prerender_trigger_type.h
index 223cccb..8012409 100644
--- a/content/public/browser/prerender_trigger_type.h
+++ b/content/public/browser/prerender_trigger_type.h
@@ -10,6 +10,8 @@
 enum class PrerenderTriggerType {
   // https://wicg.github.io/nav-speculation/prerendering.html#speculation-rules
   kSpeculationRule,
+  // Same as kSpeculationRule but triggered in isolated worlds like Extensions.
+  kSpeculationRuleFromIsolatedWorld,
   // Trigger used by content embedders.
   kEmbedder,
 };
diff --git a/content/public/test/prerender_test_util.cc b/content/public/test/prerender_test_util.cc
index eafbc1cc..a436d11b 100644
--- a/content/public/test/prerender_test_util.cc
+++ b/content/public/test/prerender_test_util.cc
@@ -304,11 +304,12 @@
   WaitForPrerenderLoadCompletion(*GetWebContents(), gurl);
 }
 
-int PrerenderTestHelper::AddPrerender(const GURL& prerendering_url) {
+int PrerenderTestHelper::AddPrerender(const GURL& prerendering_url,
+                                      int32_t world_id) {
   TRACE_EVENT("test", "PrerenderTestHelper::AddPrerender", "prerendering_url",
               prerendering_url);
   EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
-  AddPrerenderAsync(prerendering_url);
+  AddPrerenderAsync(prerendering_url, world_id);
 
   WaitForPrerenderLoadCompletion(prerendering_url);
   int host_id = GetHostForUrl(prerendering_url);
@@ -316,17 +317,23 @@
   return host_id;
 }
 
-void PrerenderTestHelper::AddPrerenderAsync(const GURL& prerendering_url) {
+void PrerenderTestHelper::AddPrerenderAsync(const GURL& prerendering_url,
+                                            int32_t world_id) {
   TRACE_EVENT("test", "PrerenderTestHelper::AddPrerenderAsync",
               "prerendering_url", prerendering_url);
   EXPECT_TRUE(content::BrowserThread::CurrentlyOn(BrowserThread::UI));
   std::string script = JsReplace(kAddSpeculationRuleScript, prerendering_url);
 
-  // Have to use ExecuteJavaScriptForTests instead of ExecJs/EvalJs here,
-  // because some test pages have ContentSecurityPolicy and EvalJs cannot work
-  // with it. See the quick migration guide for EvalJs for more information.
-  GetWebContents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
-      base::UTF8ToUTF16(script), base::NullCallback());
+  if (world_id == ISOLATED_WORLD_ID_GLOBAL) {
+    // Have to use ExecuteJavaScriptForTests instead of ExecJs/EvalJs here,
+    // because some test pages have ContentSecurityPolicy and EvalJs cannot work
+    // with it. See the quick migration guide for EvalJs for more information.
+    GetWebContents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
+        base::UTF8ToUTF16(script), base::NullCallback());
+  } else {
+    GetWebContents()->GetPrimaryMainFrame()->ExecuteJavaScriptInIsolatedWorld(
+        base::UTF8ToUTF16(script), base::NullCallback(), world_id);
+  }
 }
 
 void PrerenderTestHelper::AddMultiplePrerenderAsync(
@@ -544,6 +551,10 @@
     case content::PrerenderTriggerType::kSpeculationRule:
       DCHECK(embedder_suffix.empty());
       return std::string(histogram_base_name) + ".SpeculationRule";
+    case content::PrerenderTriggerType::kSpeculationRuleFromIsolatedWorld:
+      DCHECK(embedder_suffix.empty());
+      return std::string(histogram_base_name) +
+             ".SpeculationRuleFromIsolatedWorld";
     case content::PrerenderTriggerType::kEmbedder:
       DCHECK(!embedder_suffix.empty());
       return std::string(histogram_base_name) + ".Embedder_" + embedder_suffix;
diff --git a/content/public/test/prerender_test_util.h b/content/public/test/prerender_test_util.h
index 2233d56d..e24a27c 100644
--- a/content/public/test/prerender_test_util.h
+++ b/content/public/test/prerender_test_util.h
@@ -9,6 +9,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "content/public/browser/prerender_trigger_type.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/common/isolated_world_ids.h"
 #include "content/public/test/browser_test_utils.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -127,8 +128,10 @@
   //
   // AddPrerenderAsync() is the same as AddPrerender(), but does not wait until
   // the completion of prerendering.
-  int AddPrerender(const GURL& prerendering_url);
-  void AddPrerenderAsync(const GURL& prerendering_url);
+  int AddPrerender(const GURL& prerendering_url,
+                   int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
+  void AddPrerenderAsync(const GURL& prerendering_url,
+                         int32_t world_id = ISOLATED_WORLD_ID_GLOBAL);
   void AddPrerenderWithTargetHintAsync(const GURL& prerendering_url,
                                        const std::string& target_hint);
 
diff --git a/ios/chrome/browser/permissions/permissions_tab_helper.mm b/ios/chrome/browser/permissions/permissions_tab_helper.mm
index c2e7999b..24c2aef 100644
--- a/ios/chrome/browser/permissions/permissions_tab_helper.mm
+++ b/ios/chrome/browser/permissions/permissions_tab_helper.mm
@@ -32,7 +32,11 @@
     OverlayResponse* response) API_AVAILABLE(ios(15.0)) {
   PermissionsDialogResponse* dialog_response =
       response ? response->GetInfo<PermissionsDialogResponse>() : nullptr;
-  handler(dialog_response && dialog_response->capture_allow());
+  web::PermissionDecision decision =
+      dialog_response && dialog_response->capture_allow()
+          ? web::PermissionDecisionGrant
+          : web::PermissionDecisionDeny;
+  handler(decision);
 }
 
 }  // namespace
diff --git a/ios/chrome/browser/search_engines/BUILD.gn b/ios/chrome/browser/search_engines/BUILD.gn
index 58a78c6..8979c3f 100644
--- a/ios/chrome/browser/search_engines/BUILD.gn
+++ b/ios/chrome/browser/search_engines/BUILD.gn
@@ -139,3 +139,42 @@
   primary_script = "resources/search_engine.js"
   sources = [ "resources/search_engine.js" ]
 }
+
+source_set("eg_app_support+eg2") {
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+
+  sources = [
+    "search_engines_app_interface.h",
+    "search_engines_app_interface.mm",
+  ]
+  deps = [
+    "//base",
+    "//base/test:test_support",
+    "//components/search_engines",
+    "//ios/chrome/browser/search_engines",
+    "//ios/chrome/test/app:test_support",
+    "//ios/testing/earl_grey:eg_app_support+eg2",
+    "//ios/third_party/earl_grey2:app_framework+link",
+    "//testing/gmock",
+    "//testing/gtest:gtest",
+  ]
+}
+
+source_set("eg_test_support+eg2") {
+  configs += [
+    "//build/config/compiler:enable_arc",
+    "//build/config/ios:xctest_config",
+  ]
+  testonly = true
+
+  sources = [
+    "search_engines_app_interface.h",
+    "search_engines_app_interface_stub.mm",
+  ]
+
+  deps = [ "//ios/testing/earl_grey:eg_test_support+eg2" ]
+}
diff --git a/ios/chrome/browser/search_engines/search_engines_app_interface.h b/ios/chrome/browser/search_engines/search_engines_app_interface.h
new file mode 100644
index 0000000..ccd1834086
--- /dev/null
+++ b/ios/chrome/browser/search_engines/search_engines_app_interface.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_SEARCH_ENGINES_SEARCH_ENGINES_APP_INTERFACE_H_
+#define IOS_CHROME_BROWSER_SEARCH_ENGINES_SEARCH_ENGINES_APP_INTERFACE_H_
+
+#import <UIKit/UIKit.h>
+
+// App interface for the search engines.
+@interface SearchEnginesAppInterface : NSObject
+
+// Returns the short name of the default search engine.
++ (NSString*)defaultSearchEngine;
+
+// Resets the default search engine to `defaultSearchEngine`.
+// `defaultSearchEngine` should be its short name.
++ (void)setSearchEngineTo:(NSString*)defaultSearchEngine;
+
+// Adds a new Search engine an optionally sets it as default.
++ (void)addSearchEngineWithName:(NSString*)name
+                            URL:(NSString*)URL
+                     setDefault:(BOOL)setDefault;
+
+// Removes the search engine.
++ (void)removeSearchEngineWithName:(NSString*)name;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_SEARCH_ENGINES_SEARCH_ENGINES_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/search_engines/search_engines_app_interface.mm b/ios/chrome/browser/search_engines/search_engines_app_interface.mm
new file mode 100644
index 0000000..765b83f
--- /dev/null
+++ b/ios/chrome/browser/search_engines/search_engines_app_interface.mm
@@ -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.
+
+#import "ios/chrome/browser/search_engines/search_engines_app_interface.h"
+
+#import "base/strings/sys_string_conversions.h"
+#import "components/search_engines/template_url.h"
+#import "components/search_engines/template_url_service.h"
+#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#import "ios/chrome/test/app/chrome_test_util.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+@implementation SearchEnginesAppInterface
+
++ (NSString*)defaultSearchEngine {
+  // Get the default Search Engine.
+  ChromeBrowserState* browser_state =
+      chrome_test_util::GetOriginalBrowserState();
+  TemplateURLService* service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
+  const TemplateURL* default_provider = service->GetDefaultSearchProvider();
+  DCHECK(default_provider);
+  return base::SysUTF16ToNSString(default_provider->short_name());
+}
+
++ (void)setSearchEngineTo:(NSString*)defaultSearchEngine {
+  std::u16string defaultSearchEngineString =
+      base::SysNSStringToUTF16(defaultSearchEngine);
+  // Set the search engine back to the default in case the test fails before
+  // cleaning it up.
+  ChromeBrowserState* browser_state =
+      chrome_test_util::GetOriginalBrowserState();
+  TemplateURLService* service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
+  std::vector<TemplateURL*> urls = service->GetTemplateURLs();
+
+  for (auto iter = urls.begin(); iter != urls.end(); ++iter) {
+    if (defaultSearchEngineString == (*iter)->short_name()) {
+      service->SetUserSelectedDefaultSearchProvider(*iter);
+    }
+  }
+}
+
++ (void)addSearchEngineWithName:(NSString*)name
+                            URL:(NSString*)URL
+                     setDefault:(BOOL)setDefault {
+  ChromeBrowserState* browser_state =
+      chrome_test_util::GetOriginalBrowserState();
+  TemplateURLService* url_service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
+  TemplateURLData data;
+  data.SetShortName(base::SysNSStringToUTF16(name));
+  data.SetURL(base::SysNSStringToUTF8(URL));
+  url_service->Add(std::make_unique<TemplateURL>(data));
+  if (setDefault) {
+    [SearchEnginesAppInterface setSearchEngineTo:name];
+  }
+}
+
++ (void)removeSearchEngineWithName:(NSString*)name {
+  ChromeBrowserState* browser_state =
+      chrome_test_util::GetOriginalBrowserState();
+  TemplateURLService* url_service =
+      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
+  std::vector<TemplateURL*> urls = url_service->GetTemplateURLs();
+  std::u16string utfName = base::SysNSStringToUTF16(name);
+
+  for (auto iter = urls.begin(); iter != urls.end(); ++iter) {
+    if (utfName == (*iter)->short_name()) {
+      url_service->Remove(*iter);
+      return;
+    }
+  }
+}
+
+@end
diff --git a/ios/chrome/browser/search_engines/search_engines_app_interface_stub.mm b/ios/chrome/browser/search_engines/search_engines_app_interface_stub.mm
new file mode 100644
index 0000000..e269de3
--- /dev/null
+++ b/ios/chrome/browser/search_engines/search_engines_app_interface_stub.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/browser/search_engines/search_engines_app_interface.h"
+
+#import "ios/testing/earl_grey/earl_grey_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(SearchEnginesAppInterface)
diff --git a/ios/chrome/browser/search_engines/search_engines_util.cc b/ios/chrome/browser/search_engines/search_engines_util.cc
index a2fbfe6..61068d7 100644
--- a/ios/chrome/browser/search_engines/search_engines_util.cc
+++ b/ios/chrome/browser/search_engines/search_engines_util.cc
@@ -32,8 +32,6 @@
   std::vector<std::unique_ptr<TemplateURLData>> new_engines =
       TemplateURLPrepopulateData::GetPrepopulatedEngines(nullptr,
                                                          &default_engine_index);
-  DCHECK(default_engine_index == 0);
-  DCHECK(new_engines[0]->prepopulate_id == kGoogleEnginePrepopulatedId);
   // The aim is to replace the old search engines with the new ones.
   // It is not possible to remove all of them, because removing the current
   // selected engine is not allowed.
diff --git a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
index 73745aab..018373d8 100644
--- a/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
+++ b/ios/chrome/browser/shared/coordinator/scene/scene_controller.mm
@@ -111,6 +111,7 @@
 #import "ios/chrome/browser/ui/default_promo/default_browser_promo_non_modal_scheduler.h"
 #import "ios/chrome/browser/ui/first_run/orientation_limiting_navigation_controller.h"
 #import "ios/chrome/browser/ui/history/history_coordinator.h"
+#import "ios/chrome/browser/ui/history/history_coordinator_delegate.h"
 #import "ios/chrome/browser/ui/incognito_interstitial/incognito_interstitial_coordinator.h"
 #import "ios/chrome/browser/ui/incognito_interstitial/incognito_interstitial_coordinator_delegate.h"
 #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_scene_agent.h"
@@ -239,6 +240,7 @@
                                PolicyWatcherBrowserAgentObserving,
                                SettingsNavigationControllerDelegate,
                                SceneUIProvider,
+                               HistoryCoordinatorDelegate,
                                SceneURLLoadingServiceDelegate,
                                TabGridCoordinatorDelegate,
                                WebStateListObserving,
@@ -1405,6 +1407,7 @@
   self.historyCoordinator.loadStrategy =
       self.currentInterface.incognito ? UrlLoadStrategy::ALWAYS_IN_INCOGNITO
                                       : UrlLoadStrategy::NORMAL;
+  self.historyCoordinator.delegate = self;
   [self.historyCoordinator start];
 }
 
@@ -3379,4 +3382,17 @@
   return self.mainCoordinator.activeViewController;
 }
 
+#pragma mark - HistoryCoordinatorDelegate
+
+- (void)closeHistoryWithCompletion:(ProceduralBlock)completion {
+  __weak __typeof(self) weakSelf = self;
+  [self.historyCoordinator dismissWithCompletion:^{
+    if (completion) {
+      completion();
+    }
+    [weakSelf.historyCoordinator stop];
+    weakSelf.historyCoordinator = nil;
+  }];
+}
+
 @end
diff --git a/ios/chrome/browser/shared/public/commands/browser_commands.h b/ios/chrome/browser/shared/public/commands/browser_commands.h
index f4434b3d..7d95310d 100644
--- a/ios/chrome/browser/shared/public/commands/browser_commands.h
+++ b/ios/chrome/browser/shared/public/commands/browser_commands.h
@@ -8,16 +8,10 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
-@class ReadingListAddCommand;
-
 // Protocol for commands that will generally be handled by the "current tab",
 // which in practice is the BrowserViewController instance displaying the tab.
 @protocol BrowserCommands <NSObject>
 
-// Adds a page to the reading list using data in `command`.
-// TODO(crbug.com/1272540): Remove this command.
-- (void)addToReadingList:(ReadingListAddCommand*)command;
-
 // Prepares the browser to display the overflow menu.
 - (void)prepareForOverflowMenuPresentation;
 
diff --git a/ios/chrome/browser/sync/sync_setup_service.cc b/ios/chrome/browser/sync/sync_setup_service.cc
index 68ed0b1..6c4075c4 100644
--- a/ios/chrome/browser/sync/sync_setup_service.cc
+++ b/ios/chrome/browser/sync/sync_setup_service.cc
@@ -116,7 +116,8 @@
 }
 
 bool SyncSetupService::IsSyncRequested() const {
-  return sync_service_->GetUserSettings()->IsSyncRequested();
+  return !sync_service_->GetDisableReasons().Has(
+      syncer::SyncService::DISABLE_REASON_USER_CHOICE);
 }
 
 bool SyncSetupService::CanSyncFeatureStart() const {
@@ -127,20 +128,6 @@
   return sync_service_->GetUserSettings()->IsEncryptEverythingEnabled();
 }
 
-bool SyncSetupService::IsInitialSetupOngoing() {
-  // Sync initial setup is considered to finished iff:
-  //   1. User is signed in with sync enabled and the sync setup was completed.
-  //   OR
-  //   2. User is not signed in or has disabled sync.
-  // Otherwise we consider that the initial setup is still pending.
-  // Note that if the user visits the Advanced Settings during the opt-in flow,
-  // the Sync consent is not granted yet. In this case, IsSyncRequested() is
-  // set to true, indicating that the sync was requested but the initial setup
-  // has not been finished yet.
-  return IsSyncRequested() &&
-         !sync_service_->GetUserSettings()->IsFirstSetupComplete();
-}
-
 void SyncSetupService::PrepareForFirstSyncSetup() {
   if (!sync_blocker_)
     sync_blocker_ = sync_service_->GetSetupInProgressHandle();
diff --git a/ios/chrome/browser/sync/sync_setup_service.h b/ios/chrome/browser/sync/sync_setup_service.h
index 5351412..edcdb9b 100644
--- a/ios/chrome/browser/sync/sync_setup_service.h
+++ b/ios/chrome/browser/sync/sync_setup_service.h
@@ -46,7 +46,7 @@
   static syncer::ModelType GetModelType(SyncableDatatype datatype);
 
   // Returns whether the user wants Sync to run.
-  // TODO(crbug.com/1291946): Callers should typically use CanSyncFeatureStart()
+  // TODO(crbug.com/1291953): Callers should typically use CanSyncFeatureStart()
   // or IsSyncFeatureEnabled() instead.
   virtual bool IsSyncRequested() const;
   // Returns whether Sync-the-transport can start the Sync feature.
@@ -79,14 +79,6 @@
   // Returns whether all sync data is being encrypted.
   virtual bool IsEncryptEverythingEnabled() const;
 
-  // Returns true if the initial sync setup is currently ongoing.
-  // Returns false if it is either finished or not started.
-  // This method is guaranteed not to start the sync backend so it can be
-  // called at start-up.
-  // DEPRECATED: do not use, will be removed once the internal repository
-  // has been fixed to not use this method.
-  virtual bool IsInitialSetupOngoing();
-
   // Pauses sync allowing the user to configure what data to sync before
   // actually starting to sync data with the server.
   virtual void PrepareForFirstSyncSetup();
diff --git a/ios/chrome/browser/sync/sync_setup_service_mock.h b/ios/chrome/browser/sync/sync_setup_service_mock.h
index bc703f3..797f767 100644
--- a/ios/chrome/browser/sync/sync_setup_service_mock.h
+++ b/ios/chrome/browser/sync/sync_setup_service_mock.h
@@ -27,7 +27,6 @@
   MOCK_METHOD(bool, IsSyncingAllDataTypes, (), (const override));
   MOCK_METHOD(bool, IsDataTypePreferred, (syncer::ModelType), (const override));
   MOCK_METHOD(bool, IsDataTypeActive, (syncer::ModelType), (const override));
-  MOCK_METHOD(bool, IsInitialSetupOngoing, (), (override));
   MOCK_METHOD(void, PrepareForFirstSyncSetup, (), (override));
   MOCK_METHOD(void,
               SetFirstSetupComplete,
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_mediator.h b/ios/chrome/browser/ui/bookmarks/bookmark_mediator.h
index d972a93..07daf49 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_mediator.h
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_mediator.h
@@ -27,11 +27,15 @@
 @interface BookmarkMediator : NSObject
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)
-    initWithWithBookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel
-                        prefs:(PrefService*)prefs
-        authenticationService:(AuthenticationService*)authenticationService
-             syncSetupService:(SyncSetupService*)syncSetupService
+- (instancetype)initWithWithProfileBookmarkModel:
+                    (bookmarks::BookmarkModel*)profileBookmarkModel
+                            accountBookmarkModel:
+                                (bookmarks::BookmarkModel*)accountBookmarkModel
+                                           prefs:(PrefService*)prefs
+                           authenticationService:
+                               (AuthenticationService*)authenticationService
+                                syncSetupService:
+                                    (SyncSetupService*)syncSetupService
     NS_DESIGNATED_INITIALIZER;
 
 // Registers the feature preferences.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_mediator.mm b/ios/chrome/browser/ui/bookmarks/bookmark_mediator.mm
index 8e493ec..b9f6b8d 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_mediator.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_mediator.mm
@@ -41,46 +41,52 @@
 
 }  // namespace
 
-@interface BookmarkMediator () {
-  // Bookmark model for this mediator.
-  bookmarks::BookmarkModel* _bookmarkModel;
+@implementation BookmarkMediator {
+  // Profile bookmark model for this mediator.
+  base::WeakPtr<bookmarks::BookmarkModel> _profileBookmarkModel;
+  // Account bookmark model for this mediator.
+  base::WeakPtr<bookmarks::BookmarkModel> _accountBookmarkModel;
 
   // Prefs model for this mediator.
   PrefService* _prefs;
 
   // Authentication service for this mediator.
-  AuthenticationService* _authenticationService;
+  base::WeakPtr<AuthenticationService> _authenticationService;
 
   // The setup service for this mediator.
   SyncSetupService* _syncSetupService;
 }
 
-@end
-
-@implementation BookmarkMediator
-
 + (void)registerBrowserStatePrefs:(user_prefs::PrefRegistrySyncable*)registry {
   registry->RegisterInt64Pref(prefs::kIosBookmarkFolderDefault,
                               kLastUsedFolderNone);
 }
 
-- (instancetype)
-    initWithWithBookmarkModel:(bookmarks::BookmarkModel*)bookmarkModel
-                        prefs:(PrefService*)prefs
-        authenticationService:(AuthenticationService*)authenticationService
-             syncSetupService:(SyncSetupService*)syncSetupService {
+- (instancetype)initWithWithProfileBookmarkModel:
+                    (bookmarks::BookmarkModel*)profileBookmarkModel
+                            accountBookmarkModel:
+                                (bookmarks::BookmarkModel*)accountBookmarkModel
+                                           prefs:(PrefService*)prefs
+                           authenticationService:
+                               (AuthenticationService*)authenticationService
+                                syncSetupService:
+                                    (SyncSetupService*)syncSetupService {
   self = [super init];
   if (self) {
-    _bookmarkModel = bookmarkModel;
+    _profileBookmarkModel = profileBookmarkModel->AsWeakPtr();
+    if (accountBookmarkModel) {
+      _accountBookmarkModel = accountBookmarkModel->AsWeakPtr();
+    }
     _prefs = prefs;
-    _authenticationService = authenticationService;
+    _authenticationService = authenticationService->GetWeakPtr();
     _syncSetupService = syncSetupService;
   }
   return self;
 }
 
 - (void)disconnect {
-  _bookmarkModel = nullptr;
+  _profileBookmarkModel = nullptr;
+  _accountBookmarkModel = nullptr;
   _prefs = nullptr;
   _authenticationService = nullptr;
   _syncSetupService = nullptr;
@@ -93,8 +99,9 @@
   LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
 
   const BookmarkNode* defaultFolder = [self folderForNewBookmarks];
-  _bookmarkModel->AddNewURL(defaultFolder, defaultFolder->children().size(),
-                            base::SysNSStringToUTF16(title), URL);
+  _profileBookmarkModel->AddNewURL(defaultFolder,
+                                   defaultFolder->children().size(),
+                                   base::SysNSStringToUTF16(title), URL);
 
   MDCSnackbarMessageAction* action = [[MDCSnackbarMessageAction alloc] init];
   action.handler = editAction;
@@ -122,9 +129,9 @@
 
   for (URLWithTitle* urlWithTitle in URLs) {
     base::RecordAction(base::UserMetricsAction("BookmarkAdded"));
-    _bookmarkModel->AddNewURL(folder, folder->children().size(),
-                              base::SysNSStringToUTF16(urlWithTitle.title),
-                              urlWithTitle.URL);
+    _profileBookmarkModel->AddNewURL(
+        folder, folder->children().size(),
+        base::SysNSStringToUTF16(urlWithTitle.title), urlWithTitle.URL);
   }
 
   NSString* folderTitle = bookmark_utils_ios::TitleForBookmarkNode(folder);
@@ -140,13 +147,13 @@
 #pragma mark - Private
 
 - (const BookmarkNode*)folderForNewBookmarks {
-  const BookmarkNode* defaultFolder = _bookmarkModel->mobile_node();
+  const BookmarkNode* defaultFolder = _profileBookmarkModel->mobile_node();
   int64_t node_id = _prefs->GetInt64(prefs::kIosBookmarkFolderDefault);
   if (node_id == kLastUsedFolderNone) {
     node_id = defaultFolder->id();
   }
   const BookmarkNode* result =
-      bookmarks::GetBookmarkNodeByID(_bookmarkModel, node_id);
+      bookmarks::GetBookmarkNodeByID(_profileBookmarkModel.get(), node_id);
 
   if (result) {
     return result;
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_mediator_unittest.mm b/ios/chrome/browser/ui/bookmarks/bookmark_mediator_unittest.mm
index dd1c008..30ab1be 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_mediator_unittest.mm
@@ -54,10 +54,11 @@
     sync_setup_service_ = std::make_unique<FakeSyncSetupService>(sync_service_);
 
     mediator_ = [[BookmarkMediator alloc]
-        initWithWithBookmarkModel:profile_bookmark_model_
-                            prefs:chrome_browser_state_->GetPrefs()
-            authenticationService:authentication_service_
-                 syncSetupService:sync_setup_service_.get()];
+        initWithWithProfileBookmarkModel:profile_bookmark_model_
+                    accountBookmarkModel:nullptr
+                                   prefs:chrome_browser_state_->GetPrefs()
+                   authenticationService:authentication_service_
+                        syncSetupService:sync_setup_service_.get()];
   }
 
  protected:
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
index 2164e22..f795ec90 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.h
@@ -166,7 +166,7 @@
 // the operation wasn't successful or there's nothing to undo.
 MDCSnackbarMessage* DeleteBookmarksWithUndoToast(
     const std::set<const bookmarks::BookmarkNode*>& bookmarks,
-    const std::vector<bookmarks::BookmarkModel*> bookmark_models,
+    const std::vector<bookmarks::BookmarkModel*>& bookmark_models,
     ChromeBrowserState* browser_state);
 
 // Deletes all nodes in `bookmarks`.
diff --git a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
index 94e806ec..f4a6471c 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmark_utils_ios.mm
@@ -368,7 +368,7 @@
 
 MDCSnackbarMessage* DeleteBookmarksWithUndoToast(
     const std::set<const BookmarkNode*>& nodes,
-    const std::vector<bookmarks::BookmarkModel*> bookmark_models,
+    const std::vector<bookmarks::BookmarkModel*>& bookmark_models,
     ChromeBrowserState* browser_state) {
   CHECK_GT(bookmark_models.size(), 0u);
   size_t node_count = nodes.size();
diff --git a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
index 2f0a492..1e52e33e 100644
--- a/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
+++ b/ios/chrome/browser/ui/bookmarks/bookmarks_coordinator.mm
@@ -17,6 +17,7 @@
 #import "base/time/time.h"
 #import "components/bookmarks/browser/bookmark_model.h"
 #import "components/bookmarks/browser/bookmark_utils.h"
+#import "ios/chrome/browser/bookmarks/account_bookmark_model_factory.h"
 #import "ios/chrome/browser/bookmarks/local_or_syncable_bookmark_model_factory.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/default_browser/utils.h"
@@ -76,14 +77,7 @@
                                     BookmarksFolderEditorCoordinatorDelegate,
                                     BookmarksFolderChooserCoordinatorDelegate,
                                     BookmarksHomeViewControllerDelegate,
-                                    UIAdaptivePresentationControllerDelegate> {
-  // The browser state of the current user.
-  ChromeBrowserState* _currentBrowserState;  // weak
-
-  // The browser state to use, might be different from _currentBrowserState if
-  // it is incognito.
-  ChromeBrowserState* _browserState;  // weak
-}
+                                    UIAdaptivePresentationControllerDelegate>
 
 // The type of view controller that is being presented.
 @property(nonatomic, assign) PresentedState currentPresentedState;
@@ -98,9 +92,6 @@
 @property(nonatomic, strong)
     UINavigationController* bookmarkNavigationController;
 
-// The bookmark model in use.
-@property(nonatomic, assign) BookmarkModel* bookmarkModel;
-
 // A reference to the potentially presented bookmark browser. This will be
 // non-nil when `currentPresentedState` is BOOKMARK_BROWSER.
 @property(nonatomic, strong) BookmarksHomeViewController* bookmarkBrowser;
@@ -130,36 +121,53 @@
 
 @end
 
-@implementation BookmarksCoordinator
+@implementation BookmarksCoordinator {
+  // The browser state of the current user.
+  base::WeakPtr<ChromeBrowserState> _currentBrowserState;
+  // The browser state to use, might be different from _currentBrowserState if
+  // it is incognito.
+  base::WeakPtr<ChromeBrowserState> _browserState;
+
+  // Profile bookmark model.
+  base::WeakPtr<bookmarks::BookmarkModel> _profileBookmarkModel;
+  // Account bookmark model.
+  base::WeakPtr<bookmarks::BookmarkModel> _accountBookmarkModel;
+}
+
 @synthesize applicationCommandsHandler = _applicationCommandsHandler;
-@synthesize snackbarCommandsHandler = _snackbarCommandsHandler;
 @synthesize baseViewController = _baseViewController;
-@synthesize bookmarkBrowser = _bookmarkBrowser;
-@synthesize bookmarkModel = _bookmarkModel;
-@synthesize bookmarkNavigationController = _bookmarkNavigationController;
-@synthesize currentPresentedState = _currentPresentedState;
-@synthesize delegate = _delegate;
-@synthesize mediator = _mediator;
+@synthesize snackbarCommandsHandler = _snackbarCommandsHandler;
 
 - (instancetype)initWithBrowser:(Browser*)browser {
   self = [super initWithBaseViewController:nil browser:browser];
   if (self) {
     // Bookmarks are always opened with the main browser state, even in
     // incognito mode.
-    _currentBrowserState = browser->GetBrowserState();
-    _browserState = _currentBrowserState->GetOriginalChromeBrowserState();
-    _bookmarkModel =
+    _currentBrowserState = browser->GetBrowserState()->AsWeakPtr();
+    _browserState =
+        _currentBrowserState->GetOriginalChromeBrowserState()->AsWeakPtr();
+    _profileBookmarkModel =
         ios::LocalOrSyncableBookmarkModelFactory::GetForBrowserState(
-            _browserState);
+            _browserState.get())
+            ->AsWeakPtr();
+    BookmarkModel* accountBookmarkModel =
+        ios::AccountBookmarkModelFactory::GetForBrowserState(
+            _browserState.get());
+    if (accountBookmarkModel) {
+      _accountBookmarkModel = accountBookmarkModel->AsWeakPtr();
+    }
     _mediator = [[BookmarkMediator alloc]
-        initWithWithBookmarkModel:self.bookmarkModel
-                            prefs:_browserState->GetPrefs()
-            authenticationService:AuthenticationServiceFactory::
-                                      GetForBrowserState(_browserState)
-                 syncSetupService:SyncSetupServiceFactory::GetForBrowserState(
-                                      _browserState)];
+        initWithWithProfileBookmarkModel:_profileBookmarkModel.get()
+                    accountBookmarkModel:_accountBookmarkModel.get()
+                                   prefs:_browserState->GetPrefs()
+                   authenticationService:AuthenticationServiceFactory::
+                                             GetForBrowserState(
+                                                 _browserState.get())
+                        syncSetupService:SyncSetupServiceFactory::
+                                             GetForBrowserState(
+                                                 _browserState.get())];
     _currentPresentedState = PresentedState::NONE;
-    DCHECK(_bookmarkModel);
+    DCHECK(_profileBookmarkModel);
   }
   return self;
 }
@@ -214,7 +222,7 @@
 }
 
 - (void)bookmarkURL:(const GURL&)URL title:(NSString*)title {
-  if (!self.bookmarkModel->loaded()) {
+  if (!_profileBookmarkModel->loaded()) {
     return;
   }
 
@@ -234,12 +242,12 @@
 }
 
 - (void)presentBookmarkEditorForURL:(const GURL&)URL {
-  if (!self.bookmarkModel->loaded()) {
+  if (!_profileBookmarkModel->loaded()) {
     return;
   }
 
   const BookmarkNode* bookmark =
-      self.bookmarkModel->GetMostRecentlyAddedUserNodeForURL(URL);
+      _profileBookmarkModel->GetMostRecentlyAddedUserNodeForURL(URL);
   if (!bookmark) {
     return;
   }
@@ -247,7 +255,7 @@
 }
 
 - (void)presentBookmarks {
-  [self presentBookmarksAtDisplayedFolderNode:self.bookmarkModel->root_node()
+  [self presentBookmarksAtDisplayedFolderNode:_profileBookmarkModel->root_node()
                             selectingBookmark:nil];
 }
 
@@ -523,7 +531,7 @@
 - (void)bookmark:(BookmarkAddCommand*)command {
   DCHECK(command.URLs.count > 0) << "URLs are missing";
 
-  if (!self.bookmarkModel->loaded()) {
+  if (!_profileBookmarkModel->loaded()) {
     return;
   }
 
@@ -532,7 +540,7 @@
     DCHECK(URLWithTitle);
 
     const BookmarkNode* existingBookmark =
-        self.bookmarkModel->GetMostRecentlyAddedUserNodeForURL(
+        _profileBookmarkModel->GetMostRecentlyAddedUserNodeForURL(
             URLWithTitle.URL);
 
     if (existingBookmark) {
@@ -548,16 +556,17 @@
 }
 
 - (void)openToExternalBookmark:(BookmarkAddCommand*)command {
-  if (!self.bookmarkModel->loaded() || command.URLs.count != 1 ||
+  if (!_profileBookmarkModel->loaded() || command.URLs.count != 1 ||
       command.presentFolderChooser) {
     return;
   }
 
   const BookmarkNode* existingBookmark =
-      self.bookmarkModel->GetMostRecentlyAddedUserNodeForURL(
+      _profileBookmarkModel->GetMostRecentlyAddedUserNodeForURL(
           command.URLs.firstObject.URL);
-  [self presentBookmarksAtDisplayedFolderNode:self.bookmarkModel->mobile_node()
-                            selectingBookmark:existingBookmark];
+  [self
+      presentBookmarksAtDisplayedFolderNode:_profileBookmarkModel->mobile_node()
+                          selectingBookmark:existingBookmark];
 }
 
 #pragma mark - Private
@@ -625,7 +634,8 @@
 - (void)openURLInCurrentTab:(const GURL&)url {
   WebStateList* webStateList = self.browser->GetWebStateList();
   if (url.SchemeIs(url::kJavaScriptScheme) && webStateList) {  // bookmarklet
-    LoadJavaScriptURL(url, _browserState, webStateList->GetActiveWebState());
+    LoadJavaScriptURL(url, _browserState.get(),
+                      webStateList->GetActiveWebState());
     return;
   }
   UrlLoadParams params = UrlLoadParams::InCurrentTab(url);
@@ -662,13 +672,13 @@
   self.bookmarkBrowser.snackbarCommandsHandler = self.snackbarCommandsHandler;
 
   NSArray<BookmarksHomeViewController*>* replacementViewControllers = nil;
-  if (self.bookmarkModel->loaded()) {
+  if (_profileBookmarkModel->loaded()) {
     // Set the root node if the model has been loaded. If the model has not been
     // loaded yet, the root node will be set in BookmarksHomeViewController
     // after the model is finished loading.
     self.bookmarkBrowser.displayedFolderNode = displayedFolderNode;
     [self.bookmarkBrowser setExternalBookmark:bookmarkNode];
-    if (displayedFolderNode == self.bookmarkModel->root_node()) {
+    if (displayedFolderNode == _profileBookmarkModel->root_node()) {
       replacementViewControllers =
           [self.bookmarkBrowser cachedViewControllerStack];
     }
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 3e32059..721e8717 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -665,8 +665,6 @@
                                 dependencies:_viewControllerDependencies];
   self.tabLifecycleMediator.baseViewController = self.viewController;
   self.tabLifecycleMediator.delegate = self.viewController;
-  _viewController.readingListBrowserAgent =
-      ReadingListBrowserAgent::FromBrowser(self.browser);
 
   WebNavigationBrowserAgent::FromBrowser(self.browser)->SetDelegate(self);
 
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.h b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
index 5e0d001d0..81ef6d9 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.h
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.h
@@ -56,7 +56,6 @@
 @protocol PopupMenuUIUpdating;
 class PrerenderService;
 @class PrimaryToolbarCoordinator;
-class ReadingListBrowserAgent;
 @class SafeAreaProvider;
 @class SecondaryToolbarCoordinator;
 @class SideSwipeController;
@@ -164,9 +163,6 @@
 @property(nonatomic, weak) id<DefaultPromoNonModalPresentationDelegate>
     nonModalPromoPresentationDelegate;
 
-// TODO(crbug.com/1272540): Remove this command.
-@property(nonatomic) ReadingListBrowserAgent* readingListBrowserAgent;
-
 // Whether the receiver is currently the primary BVC.
 - (void)setPrimary:(BOOL)primary;
 
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index 16ba19ea..b6c7141 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -2747,11 +2747,6 @@
 
 #pragma mark - BrowserCommands
 
-// TODO(crbug.com/1272540): Remove this command after updating ios_internal.
-- (void)addToReadingList:(ReadingListAddCommand*)command {
-  self.readingListBrowserAgent->AddURLsToReadingList(command.URLs);
-}
-
 - (void)prepareForOverflowMenuPresentation {
   DCHECK(self.visible || self.dismissingModal);
 
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index eb46b158..d6a9159 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -328,6 +328,7 @@
     "//ios/chrome/browser/flags:system_flags",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/prefs:pref_names",
+    "//ios/chrome/browser/search_engines:eg_test_support+eg2",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/signin:capabilities_types",
     "//ios/chrome/browser/signin:fake_system_identity",
diff --git a/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.h b/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.h
index ca7c12b..80ca1443 100644
--- a/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.h
+++ b/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.h
@@ -10,13 +10,6 @@
 // App interface for the NTP.
 @interface NewTabPageAppInterface : NSObject
 
-// Returns the short name of the default search engine.
-+ (NSString*)defaultSearchEngine;
-
-// Resets the default search engine to `defaultSearchEngine`.
-// `defaultSearchEngine` should be its short name.
-+ (void)resetSearchEngineTo:(NSString*)defaultSearchEngine;
-
 // Returns the width the search field is supposed to have when the collection
 // has `collectionWidth`. `traitCollection` is the trait collection of the view
 // displaying the omnibox, its Size Class is used in the computation.
diff --git a/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.mm b/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.mm
index d1a5c9ed..6271b9a5 100644
--- a/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.mm
+++ b/ios/chrome/browser/ui/content_suggestions/new_tab_page_app_interface.mm
@@ -8,8 +8,6 @@
 #import "base/strings/sys_string_conversions.h"
 #import "base/strings/utf_string_conversions.h"
 #import "components/keyed_service/ios/browser_state_keyed_service_factory.h"
-#import "components/search_engines/template_url.h"
-#import "components/search_engines/template_url_service.h"
 #import "ios/chrome/browser/application_context/application_context.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/flags/system_flags.h"
@@ -28,35 +26,6 @@
 
 @implementation NewTabPageAppInterface
 
-+ (NSString*)defaultSearchEngine {
-  // Get the default Search Engine.
-  ChromeBrowserState* browser_state =
-      chrome_test_util::GetOriginalBrowserState();
-  TemplateURLService* service =
-      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
-  const TemplateURL* default_provider = service->GetDefaultSearchProvider();
-  DCHECK(default_provider);
-  return base::SysUTF16ToNSString(default_provider->short_name());
-}
-
-+ (void)resetSearchEngineTo:(NSString*)defaultSearchEngine {
-  std::u16string defaultSearchEngineString =
-      base::SysNSStringToUTF16(defaultSearchEngine);
-  // Set the search engine back to the default in case the test fails before
-  // cleaning it up.
-  ChromeBrowserState* browser_state =
-      chrome_test_util::GetOriginalBrowserState();
-  TemplateURLService* service =
-      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state);
-  std::vector<TemplateURL*> urls = service->GetTemplateURLs();
-
-  for (auto iter = urls.begin(); iter != urls.end(); ++iter) {
-    if (defaultSearchEngineString == (*iter)->short_name()) {
-      service->SetUserSelectedDefaultSearchProvider(*iter);
-    }
-  }
-}
-
 + (CGFloat)searchFieldWidthForCollectionWidth:(CGFloat)collectionWidth
                               traitCollection:
                                   (UITraitCollection*)traitCollection {
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 40d3b64..4e7c573 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -12,6 +12,7 @@
 #import "ios/chrome/browser/flags/chrome_switches.h"
 #import "ios/chrome/browser/ntp/features.h"
 #import "ios/chrome/browser/prefs/pref_names.h"
+#import "ios/chrome/browser/search_engines/search_engines_app_interface.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/signin/capabilities_types.h"
 #import "ios/chrome/browser/signin/fake_system_identity.h"
@@ -169,13 +170,13 @@
       setBoolValue:YES
        forUserPref:base::SysUTF8ToNSString(feed::prefs::kArticlesListVisible)];
 
-  self.defaultSearchEngine = [NewTabPageAppInterface defaultSearchEngine];
+  self.defaultSearchEngine = [SearchEnginesAppInterface defaultSearchEngine];
 }
 
 - (void)tearDown {
   [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
                                 error:nil];
-  [NewTabPageAppInterface resetSearchEngineTo:self.defaultSearchEngine];
+  [SearchEnginesAppInterface setSearchEngineTo:self.defaultSearchEngine];
 
   [super tearDown];
 }
diff --git a/ios/chrome/browser/ui/history/BUILD.gn b/ios/chrome/browser/ui/history/BUILD.gn
index d7e9d4e..28fc78b9 100644
--- a/ios/chrome/browser/ui/history/BUILD.gn
+++ b/ios/chrome/browser/ui/history/BUILD.gn
@@ -7,6 +7,7 @@
   sources = [
     "history_coordinator.h",
     "history_coordinator.mm",
+    "history_coordinator_delegate.h",
     "history_mediator.h",
     "history_mediator.mm",
   ]
diff --git a/ios/chrome/browser/ui/history/history_coordinator.h b/ios/chrome/browser/ui/history/history_coordinator.h
index 7db51cb..6caef4c 100644
--- a/ios/chrome/browser/ui/history/history_coordinator.h
+++ b/ios/chrome/browser/ui/history/history_coordinator.h
@@ -12,6 +12,7 @@
 
 enum class UrlLoadStrategy;
 
+@protocol HistoryCoordinatorDelegate;
 @protocol HistoryPresentationDelegate;
 
 // Coordinator that presents History.
@@ -24,8 +25,11 @@
 // Delegate used to make the Tab UI visible.
 @property(nonatomic, weak) id<HistoryPresentationDelegate> presentationDelegate;
 
-// Stops this Coordinator then calls `completionHandler`.
-- (void)stopWithCompletion:(ProceduralBlock)completionHandler;
+// The delegate handling coordinator dismissal.
+@property(nonatomic, weak) id<HistoryCoordinatorDelegate> delegate;
+
+// Dismisses this Coordinator then calls `completionHandler`.
+- (void)dismissWithCompletion:(ProceduralBlock)completionHandler;
 
 @end
 
diff --git a/ios/chrome/browser/ui/history/history_coordinator.mm b/ios/chrome/browser/ui/history/history_coordinator.mm
index b710d17..a7e3922 100644
--- a/ios/chrome/browser/ui/history/history_coordinator.mm
+++ b/ios/chrome/browser/ui/history/history_coordinator.mm
@@ -19,6 +19,7 @@
 #import "ios/chrome/browser/shared/ui/table_view/table_view_navigation_controller.h"
 #import "ios/chrome/browser/sync/sync_service_factory.h"
 #import "ios/chrome/browser/ui/history/history_clear_browsing_data_coordinator.h"
+#import "ios/chrome/browser/ui/history/history_coordinator_delegate.h"
 #import "ios/chrome/browser/ui/history/history_mediator.h"
 #import "ios/chrome/browser/ui/history/history_menu_provider.h"
 #import "ios/chrome/browser/ui/history/history_table_view_controller.h"
@@ -140,7 +141,13 @@
   // Disconnect the historyTableViewController before dismissing it to avoid
   // it accessing stalled objects.
   [self.historyTableViewController detachFromBrowser];
-  [self stopWithCompletion:nil];
+  [self dismissWithCompletion:nil];
+
+  // Clear C++ objects as they may reference objects that will become
+  // unavailable.
+  _browsingHistoryDriver = nullptr;
+  _browsingHistoryService = nullptr;
+  _browsingHistoryDriverDelegate = nullptr;
 }
 
 - (void)dealloc {
@@ -148,7 +155,7 @@
 }
 
 // This method should always execute the `completionHandler`.
-- (void)stopWithCompletion:(ProceduralBlock)completionHandler {
+- (void)dismissWithCompletion:(ProceduralBlock)completionHandler {
   [self.sharingCoordinator stop];
   self.sharingCoordinator = nil;
 
@@ -187,7 +194,7 @@
 #pragma mark - HistoryUIDelegate
 
 - (void)dismissHistoryWithCompletion:(ProceduralBlock)completionHandler {
-  [self stopWithCompletion:completionHandler];
+  [self.delegate closeHistoryWithCompletion:completionHandler];
 }
 
 - (void)displayPrivacySettings {
@@ -289,7 +296,7 @@
 // the active regular tab.
 - (void)onOpenedURLInNewTab {
   __weak __typeof(self) weakSelf = self;
-  [self stopWithCompletion:^{
+  [self.delegate closeHistoryWithCompletion:^{
     [weakSelf.presentationDelegate showActiveRegularTabFromHistory];
   }];
 }
@@ -298,7 +305,7 @@
 // the active incognito tab.
 - (void)onOpenedURLInNewIncognitoTab {
   __weak __typeof(self) weakSelf = self;
-  [self stopWithCompletion:^{
+  [self.delegate closeHistoryWithCompletion:^{
     [weakSelf.presentationDelegate showActiveIncognitoTabFromHistory];
   }];
 }
diff --git a/ios/chrome/browser/ui/history/history_coordinator_delegate.h b/ios/chrome/browser/ui/history/history_coordinator_delegate.h
new file mode 100644
index 0000000..d501dc83
--- /dev/null
+++ b/ios/chrome/browser/ui/history/history_coordinator_delegate.h
@@ -0,0 +1,20 @@
+// 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_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_DELEGATE_H_
+#define IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_DELEGATE_H_
+
+#import "base/ios/block_types.h"
+
+// Delegate for HistoryCoordinator.
+@protocol HistoryCoordinatorDelegate
+
+// Called when the history should be dismissed.
+// `Completion` is called after the dismissal but before the coordinator
+// is stopped.
+- (void)closeHistoryWithCompletion:(ProceduralBlock)completion;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_HISTORY_HISTORY_COORDINATOR_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/link_to_text/link_to_text_mediator_unittest.mm b/ios/chrome/browser/ui/link_to_text/link_to_text_mediator_unittest.mm
index b1c6b2b..e6273ae 100644
--- a/ios/chrome/browser/ui/link_to_text/link_to_text_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/link_to_text/link_to_text_mediator_unittest.mm
@@ -162,32 +162,30 @@
   std::unique_ptr<base::Value> CreateSuccessResponse(
       const std::string& selected_text,
       CGRect selection_rect) {
-    base::Value rect_value(base::Value::Type::DICT);
-    rect_value.SetDoubleKey("x", selection_rect.origin.x);
-    rect_value.SetDoubleKey("y", selection_rect.origin.y);
-    rect_value.SetDoubleKey("width", selection_rect.size.width);
-    rect_value.SetDoubleKey("height", selection_rect.size.height);
+    base::Value::Dict rect_value;
+    rect_value.Set("x", selection_rect.origin.x);
+    rect_value.Set("y", selection_rect.origin.y);
+    rect_value.Set("width", selection_rect.size.width);
+    rect_value.Set("height", selection_rect.size.height);
 
-    std::unique_ptr<base::Value> response_value =
-        std::make_unique<base::Value>(base::Value::Type::DICT);
-    response_value->SetDoubleKey(
-        "status", static_cast<double>(LinkGenerationOutcome::kSuccess));
-    response_value->SetKey("fragment", kTestTextFragment.ToValue());
-    response_value->SetStringKey("selectedText", selected_text);
-    response_value->SetKey("selectionRect", std::move(rect_value));
-    return response_value;
+    base::Value::Dict response_value;
+    response_value.Set("status",
+                       static_cast<double>(LinkGenerationOutcome::kSuccess));
+    response_value.Set("fragment", kTestTextFragment.ToValue());
+    response_value.Set("selectedText", selected_text);
+    response_value.Set("selectionRect", std::move(rect_value));
+    return std::make_unique<base::Value>(std::move(response_value));
   }
 
   void SetCanonicalUrl(base::Value* value, const std::string& canonical_url) {
-    value->SetStringKey("canonicalUrl", canonical_url);
+    value->GetDict().Set("canonicalUrl", canonical_url);
   }
 
   std::unique_ptr<base::Value> CreateErrorResponse(
       LinkGenerationOutcome outcome) {
-    std::unique_ptr<base::Value> response_value =
-        std::make_unique<base::Value>(base::Value::Type::DICT);
-    response_value->SetDoubleKey("status", static_cast<double>(outcome));
-    return response_value;
+    base::Value::Dict response_value;
+    response_value.Set("status", static_cast<double>(outcome));
+    return std::make_unique<base::Value>(std::move(response_value));
   }
 
   void ValidateLinkGeneratedSuccessUkm() {
@@ -403,7 +401,7 @@
 
   std::unique_ptr<base::Value> malformed_response =
       std::make_unique<base::Value>(base::Value::Type::DICT);
-  malformed_response->SetStringKey("somethingElse", "abc");
+  malformed_response->GetDict().Set("somethingElse", "abc");
   SetLinkToTextResponse(malformed_response.get(), /*zoom=*/1.0);
 
   __block BOOL callback_invoked = NO;
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
index ea9b9cc..6bd8c58ad 100644
--- a/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler_unittest.mm
@@ -24,6 +24,8 @@
 #import "ios/chrome/browser/sessions/test_session_service.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
 #import "ios/chrome/browser/shared/coordinator/scene/scene_state_browser_agent.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
+#import "ios/chrome/browser/signin/fake_authentication_service_delegate.h"
 #import "ios/chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
 #import "ios/chrome/browser/tabs/inactive_tabs/features.h"
 #import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
@@ -92,9 +94,15 @@
     test_cbs_builder.AddTestingFactory(
         ios::LocalOrSyncableBookmarkModelFactory::GetInstance(),
         ios::LocalOrSyncableBookmarkModelFactory::GetDefaultFactory());
+    test_cbs_builder.AddTestingFactory(
+        AuthenticationServiceFactory::GetInstance(),
+        AuthenticationServiceFactory::GetDefaultFactory());
 
     chrome_browser_state_ = test_cbs_builder.Build();
 
+    AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
+        chrome_browser_state_.get(),
+        std::make_unique<FakeAuthenticationServiceDelegate>());
     session_service_block_ = ^SessionServiceIOS*(id self) {
       return test_session_service_;
     };
diff --git a/ios/chrome/browser/ui/passwords/account_storage_notice/passwords_account_storage_notice_view_controller.mm b/ios/chrome/browser/ui/passwords/account_storage_notice/passwords_account_storage_notice_view_controller.mm
index 5cb0a1e..10aed41 100644
--- a/ios/chrome/browser/ui/passwords/account_storage_notice/passwords_account_storage_notice_view_controller.mm
+++ b/ios/chrome/browser/ui/passwords/account_storage_notice/passwords_account_storage_notice_view_controller.mm
@@ -34,19 +34,27 @@
 
   self.actionHandler = actionHandler;
   self.presentationController.delegate = self;
-  if (@available(iOS 15, *)) {
-    self.modalPresentationStyle = UIModalPresentationPageSheet;
-    self.sheetPresentationController.preferredCornerRadius = 20;
+
+  self.modalPresentationStyle = UIModalPresentationPageSheet;
+  self.sheetPresentationController.preferredCornerRadius = 20;
+
+  if (@available(iOS 16, *)) {
+    self.sheetPresentationController.detents = @[
+      // Add custom detent to fit content vertically.
+      self.preferredHeightDetent,
+      UISheetPresentationControllerDetent.largeDetent
+    ];
+  } else {
     self.sheetPresentationController.detents = @[
       UISheetPresentationControllerDetent.mediumDetent,
-      UISheetPresentationControllerDetent.largeDetent,
+      UISheetPresentationControllerDetent.largeDetent
     ];
-    // prefersEdgeAttachedInCompactHeight just controls attaching to the bottom,
-    // not the sheet height.
-    self.sheetPresentationController.prefersEdgeAttachedInCompactHeight = YES;
-  } else {
-    self.modalPresentationStyle = UIModalPresentationFormSheet;
   }
+
+  // prefersEdgeAttachedInCompactHeight just controls attaching to the bottom,
+  // not the sheet height.
+  self.sheetPresentationController.prefersEdgeAttachedInCompactHeight = YES;
+
   return self;
 }
 
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_account_storage_egtest.mm b/ios/chrome/browser/ui/reading_list/reading_list_account_storage_egtest.mm
index 4f1fa6e7..cb56c06 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_account_storage_egtest.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_account_storage_egtest.mm
@@ -18,6 +18,8 @@
 #error "This file requires ARC support."
 #endif
 
+using chrome_test_util::PrimarySignInButton;
+
 // Reading List integration tests for Chrome with account storage and UI
 // enabled.
 @interface ReadingListAccountStorageTestCase : WebHttpServerChromeTestCase
@@ -36,6 +38,35 @@
 
 #pragma mark - ReadingListAccountStorageTestCase Tests
 
+// Tests that the sign-in is re-shown after the user signs-in and then signs-out
+// while the reading list screen is still shown.
+// See http://crbug.com/1432611.
+- (void)testPromoReshowAfterSignInAndSignOut {
+  FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
+  [SigninEarlGrey addFakeIdentity:fakeIdentity1];
+  // Sign-in with identity1 with the promo.
+  [ReadingListEarlGreyUI openReadingList];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
+  [[EarlGrey
+      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
+                                          grey_sufficientlyVisible(), nil)]
+      performAction:grey_tap()];
+  // Verify that identity1 is signed-in and the promo is hidden.
+  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity1.userEmail
+                                        consent:signin::ConsentLevel::kSignin];
+  [SigninEarlGreyUI verifySigninPromoNotVisible];
+  // Sign-out without changing the UI and verify that the promo is shown,
+  // without spinner.
+  [SigninEarlGrey signOut];
+  [SigninEarlGreyUI
+      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
+  [[EarlGrey
+      selectElementWithMatcher:grey_allOf(grey_accessibilityID(
+                                              kSigninPromoActivityIndicatorId),
+                                          grey_sufficientlyVisible(), nil)]
+      assertWithMatcher:grey_nil()];
+}
 // Tests to sign-in in incognito mode with the promo.
 // See http://crbug.com/1432747.
 - (void)testSignInPromoInIncognito {
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
index 26a112f..ffdabb3 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_coordinator.mm
@@ -559,7 +559,7 @@
 // Called when a user changes the syncing state.
 - (void)onPrimaryAccountChanged:
     (const signin::PrimaryAccountChangeEvent&)event {
-  switch (event.GetEventTypeFor(signin::ConsentLevel::kSync)) {
+  switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
     case signin::PrimaryAccountChangeEvent::Type::kSet:
       if (!_signinPromoViewMediator.signinInProgress) {
         self.shouldShowSignInPromo = NO;
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index 350a0fa..13b8d5c 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -349,15 +349,6 @@
   return UITableViewAutomaticDimension;
 }
 
-- (CGFloat)tableView:(UITableView*)tableView
-    heightForFooterInSection:(NSInteger)section {
-  if ([self.tableViewModel sectionIdentifierForSectionIndex:section] ==
-      SectionIdentifierSignInPromo) {
-    return 0;
-  }
-  return UITableViewAutomaticDimension;
-}
-
 #pragma mark - TableViewURLDragDataSource
 
 - (URLInfo*)tableView:(UITableView*)tableView
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
index 0940dcf..c41c48e 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
@@ -462,6 +462,10 @@
   return YES;
 }
 
+- (BOOL)notesEnabled {
+  return YES;
+}
+
 - (GREYElementInteraction*)
     interactionForSinglePasswordEntryWithDomain:(NSString*)domain
                                        username:(NSString*)username {
@@ -545,16 +549,10 @@
         password_manager::features::kEnablePasswordsAccountStorage);
   }
 
-  if ([self isRunningTest:@selector(testLayoutWithNotesDisabled)]) {
-    config.features_disabled.push_back(syncer::kPasswordNotesWithBackup);
-  }
-  if ([self isRunningTest:@selector(testLayoutWithNotesEnabled)] ||
-      [self isRunningTest:@selector(testAddPasswordLayoutWithLongNotes)] ||
-      [self isRunningTest:@selector
-            (testAddPasswordSaveButtonStateOnFieldChanges)] ||
-      [self isRunningTest:@selector(testLayoutWithLongNotes)] ||
-      [self isRunningTest:@selector(testShowHidePasswordWithNotesEnabled)]) {
+  if ([self notesEnabled]) {
     config.features_enabled.push_back(syncer::kPasswordNotesWithBackup);
+  } else {
+    config.features_disabled.push_back(syncer::kPasswordNotesWithBackup);
   }
 
   return config;
@@ -574,6 +572,13 @@
       performAction:grey_tap()];
 
   // Inspect "password details" view.
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
@@ -590,6 +595,11 @@
 // Checks that attempts to copy a password provide appropriate feedback,
 // both when reauthentication succeeds and when it fails.
 - (void)testCopyPasswordToast {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is not needed with notes for passwords enabled.");
+  }
+
   // Saving a form is needed for using the "password details" view.
   SaveExamplePasswordForm();
 
@@ -640,13 +650,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [GetInteractionForPasswordDetailItem(ShowPasswordButton())
       performAction:grey_tap()];
@@ -666,6 +685,11 @@
 // Checks that an attempt to show a password provides an appropriate feedback
 // when reauthentication fails.
 - (void)testShowPasswordToastAuthFailed {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is not needed with notes for passwords enabled.");
+  }
+
   // Saving a form is needed for using the "password details" view.
   SaveExamplePasswordForm();
 
@@ -706,6 +730,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
@@ -732,6 +763,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
@@ -762,13 +800,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
@@ -824,13 +871,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -896,13 +952,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
@@ -971,13 +1036,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
@@ -1133,13 +1207,22 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
@@ -1214,6 +1297,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
@@ -1225,9 +1315,11 @@
   // Make sure to capture the reauthentication module in a variable until the
   // end of the test, otherwise it might get deleted too soon and break the
   // functionality of copying and viewing passwords.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   // Tap the context menu item for copying.
   [[EarlGrey
@@ -1264,6 +1356,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"federated username"]
       performAction:grey_tap()];
@@ -1283,9 +1382,12 @@
       assertWithMatcher:grey_nil()];
 
   // Check that editing doesn't require reauth.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kFailure];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kFailure];
+  }
+
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
   // Ensure delete button is present after entering editing mode.
@@ -1309,6 +1411,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
@@ -1345,6 +1454,10 @@
 // Checks the order of the elements in the detail view layout for a
 // non-federated, non-blocked credential with notes feature disabled.
 - (void)testLayoutWithNotesDisabled {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(@"This test is obsolete with notes enabled.");
+  }
+
   SaveExamplePasswordForm();
 
   OpenPasswordManager();
@@ -1385,6 +1498,11 @@
 // Checks the order of the elements in the detail view layout for a
 // non-federated, non-blocked credential with notes feature enabled.
 - (void)testLayoutWithNotesEnabled {
+  if (![self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolete with notes for passwords disabled.");
+  }
+
   SaveExamplePasswordFormWithNote();
 
   OpenPasswordManager();
@@ -1429,6 +1547,11 @@
 // Checks that entering too long note while editing a password blocks the save
 // button and displays a footer explanation.
 - (void)testLayoutWithLongNotes {
+  if (![self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolete with notes for passwords disabled.");
+  }
+
   SaveExamplePasswordFormWithNote();
 
   OpenPasswordManager();
@@ -1521,6 +1644,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"federated username"]
       performAction:grey_tap()];
@@ -1675,6 +1805,11 @@
 // Checks that an attempt to copy a password provides appropriate feedback when
 // reauthentication cannot be attempted.
 - (void)testCopyPasswordToastNoReauth {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is not needed with notes for passwords enabled.");
+  }
+
   // Saving a form is needed for using the "password details" view.
   SaveExamplePasswordForm();
 
@@ -1706,6 +1841,11 @@
 // Checks that an attempt to view a password provides appropriate feedback when
 // reauthentication cannot be attempted.
 - (void)testShowPasswordToastNoReauth {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is not needed with notes for passwords enabled.");
+  }
+
   // Saving a form is needed for using the "password details" view.
   SaveExamplePasswordForm();
 
@@ -1774,6 +1914,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   // Wait for the loading indicator to disappear, and the sections to be on
   // screen, before scrolling.
   [[EarlGrey selectElementWithMatcher:SavedPasswordsHeaderMatcher()]
@@ -2114,14 +2261,23 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -2166,14 +2322,23 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -2241,14 +2406,23 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username1"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -2283,14 +2457,23 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"concrete username"]
       performAction:grey_tap()];
 
   // Check the snackbar in case of successful reauthentication.
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -2453,6 +2636,11 @@
 // Checks that entering too long note while adding passwords blocks the save
 // button and displays a footer explanation.
 - (void)testAddPasswordLayoutWithLongNotes {
+  if (![self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolote with notes for passwords disabled.");
+  }
+
   OpenPasswordManager();
 
   [[EarlGrey selectElementWithMatcher:AddPasswordToolbarButton()]
@@ -2610,6 +2798,11 @@
 // from invalid to valid input in any of the fields (website, password, note),
 // when there are still other fields with invalid input.
 - (void)testAddPasswordSaveButtonStateOnFieldChanges {
+  if (![self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolete with notes for passwords disabled.");
+  }
+
   OpenPasswordManager();
   [[EarlGrey selectElementWithMatcher:AddPasswordToolbarButton()]
       performAction:grey_tap()];
@@ -2720,13 +2913,22 @@
   [[EarlGrey selectElementWithMatcher:AddPasswordSaveButton()]
       performAction:grey_tap()];
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [[self interactionForSinglePasswordEntryWithDomain:@"example.com"
                                             username:@"new username"]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   TapEdit();
 
@@ -2828,6 +3030,11 @@
 }
 
 - (void)testShowHidePassword {
+  if ([self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolete with notes for passwords enabled.");
+  }
+
   SaveExamplePasswordForm();
 
   OpenPasswordManager();
@@ -2859,6 +3066,11 @@
 // enabled since the reauthentication happens before navigating to the details
 // view in this scenario.
 - (void)testShowHidePasswordWithNotesEnabled {
+  if (![self notesEnabled]) {
+    EARL_GREY_TEST_SKIPPED(
+        @"This test is obsolete with notes for passwords disabled.");
+  }
+
   SaveExamplePasswordForm();
 
   OpenPasswordManager();
@@ -2900,6 +3112,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   // Make sure the cell is loaded properly before tapping on it.
   ConditionBlock condition = ^{
     NSError* error = nil;
@@ -3075,14 +3294,23 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   [GetInteractionForPasswordEntry(@"example1.com, 2 accounts")
       assertWithMatcher:grey_notNil()];
   [GetInteractionForPasswordEntry(@"example1.com, 2 accounts")
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:NavigationBarEditButton()]
       performAction:grey_tap()];
@@ -3236,6 +3464,13 @@
                                 enableSync:NO];
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   // `passwordMatcher` includes grey_sufficientlyVisible() because there are
   // other invisible cells when password details is closed later.
   id<GREYMatcher> passwordMatcher =
@@ -3252,9 +3487,11 @@
   [[EarlGrey selectElementWithMatcher:passwordMatcher]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                           kMovePasswordToAccountButtonId)]
@@ -3291,6 +3528,13 @@
 
   OpenPasswordManager();
 
+  if ([self notesEnabled]) {
+    [PasswordSettingsAppInterface
+        setUpMockReauthenticationModuleForPasswordManager];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
+
   // `passwordMatcher` includes grey_sufficientlyVisible() because there are
   // other invisible cells when password details is closed later.
   id<GREYMatcher> passwordMatcher =
@@ -3307,9 +3551,11 @@
   [[EarlGrey selectElementWithMatcher:passwordMatcher]
       performAction:grey_tap()];
 
-  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kSuccess];
+  if (![self notesEnabled]) {
+    [PasswordSettingsAppInterface setUpMockReauthenticationModule];
+    [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                      ReauthenticationResult::kSuccess];
+  }
 
   [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                           kMovePasswordToAccountButtonId)]
@@ -3373,3 +3619,23 @@
 }
 
 @end
+
+// Rerun all the tests in this file but with kPasswordNotesWithBackup disabled.
+// This will be removed once that feature launches fully, but ensures
+// regressions aren't introduced in the meantime.
+@interface PasswordManagerNotesDisabledTestCase : PasswordManagerTestCase
+
+@end
+
+@implementation PasswordManagerNotesDisabledTestCase
+
+- (BOOL)notesEnabled {
+  return NO;
+}
+
+// This causes the test case to actually be detected as a test case. The actual
+// tests are all inherited from the parent class.
+- (void)testEmpty {
+}
+
+@end
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index aa07cd7..8b799fc 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -1411,7 +1411,7 @@
           [[ContentSettingsTableViewController alloc] initWithBrowser:_browser];
       break;
     case SettingsItemTypeTabs:
-      // TODO(crbug.com/1418021): Add metrics about tabs settings.
+      base::RecordAction(base::UserMetricsAction("Settings.Tabs"));
       [self showTabsSettings];
       break;
     case SettingsItemTypeBandwidth:
diff --git a/ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_table_view_controller.mm
index 140a6df5..ae79d49 100644
--- a/ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_table_view_controller.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/settings/tabs/inactive_tabs/inactive_tabs_settings_table_view_controller.h"
 
 #import "base/i18n/message_formatter.h"
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
 #import "base/notreached.h"
 #import "base/strings/sys_string_conversions.h"
 #import "base/time/time.h"
@@ -176,13 +178,12 @@
 #pragma mark - SettingsControllerProtocol
 
 - (void)reportDismissalUserAction {
-  // TODO(crbug.com/1418021): Add metrics when the user go close Inactive Tabs
-  // Settings.
+  base::RecordAction(
+      base::UserMetricsAction("MobileInactiveTabsSettingsClose"));
 }
 
 - (void)reportBackUserAction {
-  // TODO(crbug.com/1418021): Add metrics when the user go back from Inactive
-  // Tabs Settings to Tabs Settings screen.
+  base::RecordAction(base::UserMetricsAction("MobileInactiveTabsSettingsBack"));
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/tabs/tabs_settings_mediator.mm b/ios/chrome/browser/ui/settings/tabs/tabs_settings_mediator.mm
index 9e4b4dd..7082f803 100644
--- a/ios/chrome/browser/ui/settings/tabs/tabs_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/tabs/tabs_settings_mediator.mm
@@ -4,6 +4,8 @@
 
 #import "ios/chrome/browser/ui/settings/tabs/tabs_settings_mediator.h"
 
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
 #import "components/prefs/ios/pref_observer_bridge.h"
 #import "components/prefs/pref_change_registrar.h"
 #import "components/prefs/pref_service.h"
@@ -76,8 +78,7 @@
 
 - (void)tabsSettingsTableViewControllerDidSelectInactiveTabsSettings:
     (TabsSettingsTableViewController*)tabsSettingsTableViewController {
-  // TODO(crbug.com/1418021): Add metrics when the user go to inactive tabs
-  // settings.
+  base::RecordAction(base::UserMetricsAction("Settings.Tabs.InactiveTabs"));
   [self.handler showInactiveTabsSettings];
 }
 
diff --git a/ios/chrome/browser/ui/settings/tabs/tabs_settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/tabs/tabs_settings_table_view_controller.mm
index 967a7dd..dfe4a9e 100644
--- a/ios/chrome/browser/ui/settings/tabs/tabs_settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/tabs/tabs_settings_table_view_controller.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/settings/tabs/tabs_settings_table_view_controller.h"
 
 #import "base/i18n/message_formatter.h"
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
 #import "base/strings/sys_string_conversions.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
@@ -76,12 +78,11 @@
 #pragma mark - SettingsControllerProtocol
 
 - (void)reportDismissalUserAction {
-  // TODO(crbug.com/1418021): Add metrics when the user go close Tabs Settings.
+  base::RecordAction(base::UserMetricsAction("MobileTabsSettingsClose"));
 }
 
 - (void)reportBackUserAction {
-  // TODO(crbug.com/1418021): Add metrics when the user go back from Tabs
-  // Settings to root Settings screen.
+  base::RecordAction(base::UserMetricsAction("MobileTabsSettingsBack"));
 }
 
 #pragma mark - UITableViewDelegate
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
index ed03f47..82b9d2f 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_coordinator.mm
@@ -56,6 +56,7 @@
 #import "ios/chrome/browser/ui/gestures/view_controller_trait_collection_observer.h"
 #import "ios/chrome/browser/ui/gestures/view_revealing_vertical_pan_handler.h"
 #import "ios/chrome/browser/ui/history/history_coordinator.h"
+#import "ios/chrome/browser/ui/history/history_coordinator_delegate.h"
 #import "ios/chrome/browser/ui/history/public/history_presentation_delegate.h"
 #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_mediator.h"
 #import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_scene_agent.h"
@@ -102,6 +103,7 @@
                                   InactiveTabsCoordinatorDelegate,
                                   SceneStateObserver,
                                   SnackbarCoordinatorDelegate,
+                                  HistoryCoordinatorDelegate,
                                   TabContextMenuDelegate,
                                   TabGridMediatorDelegate,
                                   TabPresentationDelegate,
@@ -333,7 +335,7 @@
   }
   // History may be presented on top of the tab grid.
   if (self.historyCoordinator) {
-    [self.historyCoordinator stopWithCompletion:completion];
+    [self closeHistoryWithCompletion:completion];
   } else if (completion) {
     completion();
   }
@@ -937,6 +939,9 @@
   self.inactiveTabsButtonMediator = nil;
   [self.inactiveTabsCoordinator stop];
   self.inactiveTabsCoordinator = nil;
+
+  [self.historyCoordinator stop];
+  self.historyCoordinator = nil;
 }
 
 #pragma mark - TabPresentationDelegate
@@ -1128,6 +1133,7 @@
   self.historyCoordinator.loadStrategy =
       UrlLoadStrategy::ALWAYS_NEW_FOREGROUND_TAB;
   self.historyCoordinator.presentationDelegate = self;
+  self.historyCoordinator.delegate = self;
   [self.historyCoordinator start];
 }
 
@@ -1213,6 +1219,19 @@
   [self showActiveRegularTabFromRecentTabs];
 }
 
+#pragma mark - HistoryCoordinatorDelegate
+
+- (void)closeHistoryWithCompletion:(ProceduralBlock)completion {
+  __weak __typeof(self) weakSelf = self;
+  [self.historyCoordinator dismissWithCompletion:^{
+    if (completion) {
+      completion();
+    }
+    [weakSelf.historyCoordinator stop];
+    weakSelf.historyCoordinator = nil;
+  }];
+}
+
 #pragma mark - TabContextMenuDelegate
 
 - (void)shareURL:(const GURL&)URL
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
index bdc8f67..6c668a6 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
@@ -1330,7 +1330,7 @@
   // Set the content size to be large enough to contain all the tabs at the
   // desired width, with the standard overlap, plus the new tab button.
   CGSize contentSize = CGSizeMake(
-      (_currentTabWidth * tabCount) - ([self tabOverlap] * tabCount) +
+      (_currentTabWidth * tabCount) - ([self tabOverlap] * (tabCount - 1)) +
           CGRectGetWidth([_buttonNewTab frame]) - kNewTabOverlap,
       tabHeight);
   if (CGSizeEqualToSize([_tabStripView contentSize], contentSize))
diff --git a/ios/chrome/browser/web/web_state_delegate_browser_agent.h b/ios/chrome/browser/web/web_state_delegate_browser_agent.h
index 1f86314..8e44cdb 100644
--- a/ios/chrome/browser/web/web_state_delegate_browser_agent.h
+++ b/ios/chrome/browser/web/web_state_delegate_browser_agent.h
@@ -93,7 +93,7 @@
       base::OnceCallback<void(bool)> callback) override;
   web::JavaScriptDialogPresenter* GetJavaScriptDialogPresenter(
       web::WebState* source) override;
-  bool HandlePermissionsDecisionRequest(
+  void HandlePermissionsDecisionRequest(
       web::WebState* source,
       NSArray<NSNumber*>* permissions,
       web::WebStatePermissionDecisionHandler handler) override
diff --git a/ios/chrome/browser/web/web_state_delegate_browser_agent.mm b/ios/chrome/browser/web/web_state_delegate_browser_agent.mm
index b1a64c3..ecebc45 100644
--- a/ios/chrome/browser/web/web_state_delegate_browser_agent.mm
+++ b/ios/chrome/browser/web/web_state_delegate_browser_agent.mm
@@ -233,19 +233,17 @@
   return &java_script_dialog_presenter_;
 }
 
-bool WebStateDelegateBrowserAgent::HandlePermissionsDecisionRequest(
+void WebStateDelegateBrowserAgent::HandlePermissionsDecisionRequest(
     web::WebState* source,
     NSArray<NSNumber*>* permissions,
-    web::WebStatePermissionDecisionHandler handler) {
-  if (@available(iOS 15.0, *)) {
-    if (web::features::IsMediaPermissionsControlEnabled()) {
-      PermissionsTabHelper::FromWebState(source)
-          ->PresentPermissionsDecisionDialogWithCompletionHandler(permissions,
-                                                                  handler);
-      return true;
-    }
+    web::WebStatePermissionDecisionHandler handler) API_AVAILABLE(ios(15.0)) {
+  if (web::features::IsMediaPermissionsControlEnabled()) {
+    PermissionsTabHelper::FromWebState(source)
+        ->PresentPermissionsDecisionDialogWithCompletionHandler(permissions,
+                                                                handler);
+  } else {
+    handler(web::PermissionDecisionShowDefaultPrompt);
   }
-  return false;
 }
 
 void WebStateDelegateBrowserAgent::OnAuthRequired(
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
index a4b645c..d80fe78 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.h
@@ -132,6 +132,12 @@
 // nothing.
 - (void)customizeSubtitle:(UITextView*)subtitle;
 
+// Detent that attempts to fit the preferred height of the content. Detent may
+// be inactive in some size classes, so it should be used together with at
+// least one other detent.
+- (UISheetPresentationControllerDetent*)
+    preferredHeightDetent API_AVAILABLE(ios(16));
+
 @end
 
 #endif  // IOS_CHROME_COMMON_UI_CONFIRMATION_ALERT_CONFIRMATION_ALERT_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
index 275ec82..690f5b12 100644
--- a/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
+++ b/ios/chrome/common/ui/confirmation_alert/confirmation_alert_view_controller.mm
@@ -88,6 +88,17 @@
 
 #pragma mark - Public
 
+- (UISheetPresentationControllerDetent*)preferredHeightDetent {
+  __typeof(self) __weak weakSelf = self;
+  auto resolver = ^CGFloat(
+      id<UISheetPresentationControllerDetentResolutionContext> context) {
+    return [weakSelf detentForPreferredHeightInContext:context];
+  };
+  return [UISheetPresentationControllerDetent
+      customDetentWithIdentifier:@"preferred_height"
+                        resolver:resolver];
+}
+
 - (instancetype)init {
   self = [super initWithNibName:nil bundle:nil];
   if (self) {
@@ -770,4 +781,42 @@
   return tertiaryActionButton;
 }
 
+- (CGFloat)detentForPreferredHeightInContext:
+    (id<UISheetPresentationControllerDetentResolutionContext>)context
+    API_AVAILABLE(ios(16)) {
+  // Only activate this detent in portrait orientation on iPhone.
+  UITraitCollection* traitCollection = context.containerTraitCollection;
+  if (traitCollection.horizontalSizeClass != UIUserInterfaceSizeClassCompact ||
+      traitCollection.verticalSizeClass != UIUserInterfaceSizeClassRegular) {
+    return UISheetPresentationControllerDetentInactive;
+  }
+
+  // Obtain container view from presentation controller directly because
+  // this view may not have been added to its container view yet.
+  UIView* containerView = self.sheetPresentationController.containerView;
+
+  // Measure compressed height without safe area inset (detent values are
+  // generally expressed without safe area insets).
+  CGFloat fittingWidth = containerView.bounds.size.width;
+  CGSize fittingSize =
+      CGSizeMake(fittingWidth, UILayoutFittingCompressedSize.height);
+  CGFloat height = [self.view systemLayoutSizeFittingSize:fittingSize].height;
+  height -= containerView.safeAreaInsets.bottom;
+
+  // Replace bottom margin calculated based on view's own safe area with bottom
+  // margin calculated based on the safe area of the container view it will
+  // eventually live in. This is needed in case the detent value is requested
+  // before the view has been added to its superview.
+  height -= MAX(kActionsBottomMargin, self.view.safeAreaInsets.bottom);
+  height += MAX(kActionsBottomMargin, containerView.safeAreaInsets.bottom);
+
+  // Make sure detent is not larger than 75% of the maximum detent value but at
+  // least as large as a standard medium detent.
+  height = MIN(height, 0.75 * context.maximumDetentValue);
+  CGFloat mediumDetentHeight = [UISheetPresentationControllerDetent.mediumDetent
+      resolvedValueInContext:context];
+  height = MAX(height, mediumDetentHeight);
+  return height;
+}
+
 @end
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 34d07f4..4cd47332 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -97,6 +97,7 @@
     "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/passwords:eg_app_support+eg2",
     "//ios/chrome/browser/policy:eg_app_support+eg2",
+    "//ios/chrome/browser/search_engines:eg_app_support+eg2",
     "//ios/chrome/browser/search_engines:search_engines_util",
     "//ios/chrome/browser/search_engines:template_url_service_factory",
     "//ios/chrome/browser/sessions:restoration_agent",
diff --git a/ios/web/public/permissions/README.md b/ios/web/public/permissions/README.md
index cb5ced44..5f42ed73 100644
--- a/ios/web/public/permissions/README.md
+++ b/ios/web/public/permissions/README.md
@@ -1,3 +1,4 @@
-This directory contains two enums:
+This directory contains three enums:
   - Permission specifies different data or device hardwares that the app/site needs access permissions to; currently we have camera and microphone.
   - PermissionState specifies whether the user has granted the site permission to use a certain type of permission.
+  - PermissionDecision an enumeration based on `WKPermissionDecision` to specifies the possible permission decisions for device resource access
diff --git a/ios/web/public/permissions/permissions.h b/ios/web/public/permissions/permissions.h
index be34853..e0a76df 100644
--- a/ios/web/public/permissions/permissions.h
+++ b/ios/web/public/permissions/permissions.h
@@ -9,9 +9,17 @@
 
 namespace web {
 
+// Enum based on `WKPermissionDecision` to specify the possible permission
+// decisions for device resource access.
+typedef NS_ENUM(NSInteger, PermissionDecision) {
+  PermissionDecisionShowDefaultPrompt,
+  PermissionDecisionGrant,
+  PermissionDecisionDeny,
+};
+
 // Callback that processes user's permission for a web state to asks the user
-// whether it is allowed to access certain permissions on the device.
-using WebStatePermissionDecisionHandler = void (^)(BOOL allow);
+// the decision to access certain permissions on the device.
+using WebStatePermissionDecisionHandler = void (^)(PermissionDecision decision);
 
 // Enum specifying different data or device hardwares that the app/site needs
 // access permissions to.
diff --git a/ios/web/public/test/crw_fake_web_state_delegate.mm b/ios/web/public/test/crw_fake_web_state_delegate.mm
index 37f4faf..cc0a68b0 100644
--- a/ios/web/public/test/crw_fake_web_state_delegate.mm
+++ b/ios/web/public/test/crw_fake_web_state_delegate.mm
@@ -61,14 +61,13 @@
   return nil;
 }
 
-- (BOOL)webState:(web::WebState*)webState
+- (void)webState:(web::WebState*)webState
     handlePermissions:(NSArray<NSNumber*>*)permissions
-      decisionHandler:(void (^)(BOOL allow))decisionHandler
+      decisionHandler:(web::WebStatePermissionDecisionHandler)decisionHandler
     API_AVAILABLE(ios(15.0)) {
   _webState = webState;
   _permissionsRequestHandled = YES;
-  decisionHandler(YES);
-  return YES;
+  decisionHandler(web::PermissionDecisionGrant);
 }
 
 - (void)webState:(web::WebState*)webState
diff --git a/ios/web/public/test/fakes/fake_web_state_delegate.h b/ios/web/public/test/fakes/fake_web_state_delegate.h
index 835828b..d9f6533f 100644
--- a/ios/web/public/test/fakes/fake_web_state_delegate.h
+++ b/ios/web/public/test/fakes/fake_web_state_delegate.h
@@ -87,7 +87,7 @@
                       NSURLProtectionSpace* protection_space,
                       NSURLCredential* proposed_credential,
                       AuthCallback callback) override;
-  bool HandlePermissionsDecisionRequest(
+  void HandlePermissionsDecisionRequest(
       WebState* source,
       NSArray<NSNumber*>* permissions,
       WebStatePermissionDecisionHandler handler) override
@@ -153,10 +153,10 @@
   // `HandlePermissionsDecisionRequest`.
   void ClearLastRequestedPermissions() { last_requested_permissions_ = nil; }
 
-  // Sets that whether permissions should be granted or denied the next time
+  // Sets that permission decision the for next time
   // `HandlePermissionsDecisionRequest` is called.
-  void SetShouldGrantPermissions(bool should_grant_permissions) {
-    should_grant_permissions_ = should_grant_permissions;
+  void SetPermissionDecision(PermissionDecision permission_decision) {
+    permission_decision_ = permission_decision;
   }
 
   // Sets the return value of `ShouldAllowAppLaunching`.
@@ -181,7 +181,7 @@
   std::unique_ptr<FakeAuthenticationRequest> last_authentication_request_;
   NSArray<NSNumber*>* last_requested_permissions_;
   bool should_allow_app_launching_ = false;
-  bool should_grant_permissions_ = false;
+  PermissionDecision permission_decision_ = PermissionDecisionDeny;
 };
 
 }  // namespace web
diff --git a/ios/web/public/test/fakes/fake_web_state_delegate.mm b/ios/web/public/test/fakes/fake_web_state_delegate.mm
index dcbd258..20658922 100644
--- a/ios/web/public/test/fakes/fake_web_state_delegate.mm
+++ b/ios/web/public/test/fakes/fake_web_state_delegate.mm
@@ -115,13 +115,12 @@
   last_authentication_request_->auth_callback = std::move(callback);
 }
 
-bool FakeWebStateDelegate::HandlePermissionsDecisionRequest(
+void FakeWebStateDelegate::HandlePermissionsDecisionRequest(
     WebState* source,
     NSArray<NSNumber*>* permissions,
     WebStatePermissionDecisionHandler handler) {
   last_requested_permissions_ = permissions;
-  handler(should_grant_permissions_);
-  return true;
+  handler(permission_decision_);
 }
 
 }  // namespace web
diff --git a/ios/web/public/web_state_delegate.h b/ios/web/public/web_state_delegate.h
index 38341a58..dfbe87e 100644
--- a/ios/web/public/web_state_delegate.h
+++ b/ios/web/public/web_state_delegate.h
@@ -60,13 +60,13 @@
   virtual JavaScriptDialogPresenter* GetJavaScriptDialogPresenter(
       WebState* source);
 
-  // Returns whether the delegate is able to handle requests the user's
-  // permission to access `web::Permission`.
+  // Called when web resource requests the user's permission to access
+  // `web::Permission`.
   //
-  // If returned `true`, the delegate must use the `handler` function to answer
-  // to the permissions access request; otherwise, the delegate must NOT use the
-  // handler.
-  virtual bool HandlePermissionsDecisionRequest(
+  // The delegate should use the `handler` function to answer to the request to
+  // grant, deny media permissions or show the default prompt that asks for
+  // permissions.
+  virtual void HandlePermissionsDecisionRequest(
       WebState* source,
       NSArray<NSNumber*>* permissions,
       WebStatePermissionDecisionHandler handler) API_AVAILABLE(ios(15.0));
diff --git a/ios/web/public/web_state_delegate_bridge.h b/ios/web/public/web_state_delegate_bridge.h
index 3925a00..1537dd71 100644
--- a/ios/web/public/web_state_delegate_bridge.h
+++ b/ios/web/public/web_state_delegate_bridge.h
@@ -47,16 +47,11 @@
     (web::WebState*)webState;
 
 // Called when the media permission is requested and to acquire the decision
-// handler needed to process the user's decision to grant or deny media
-// permissions.
-//
-// If the delegate doesn't implement this method or delegate returned `NO`, the
-// web state would still show the default prompt that asks for permissions. If
-// delegate returned `YES`, the delegate must use the `handler` function to
-// answer to the permissions access request;
-- (BOOL)webState:(web::WebState*)webState
+// handler needed to process the user's decision to grant, deny media
+// permissions or show the default prompt that asks for permissions.
+- (void)webState:(web::WebState*)webState
     handlePermissions:(NSArray<NSNumber*>*)permissions
-      decisionHandler:(void (^)(BOOL allow))decisionHandler
+      decisionHandler:(web::WebStatePermissionDecisionHandler)decisionHandler
     API_AVAILABLE(ios(15.0));
 
 // Called when a request receives an authentication challenge specified by
@@ -115,7 +110,7 @@
       base::OnceCallback<void(bool)> callback) override;
   JavaScriptDialogPresenter* GetJavaScriptDialogPresenter(
       WebState* source) override;
-  bool HandlePermissionsDecisionRequest(
+  void HandlePermissionsDecisionRequest(
       WebState* source,
       NSArray<NSNumber*>* permissions,
       WebStatePermissionDecisionHandler handler)
diff --git a/ios/web/web_state/permissions_inttest.mm b/ios/web/web_state/permissions_inttest.mm
index c97c44f..fd2e13b 100644
--- a/ios/web/web_state/permissions_inttest.mm
+++ b/ios/web/web_state/permissions_inttest.mm
@@ -132,7 +132,7 @@
     EXPECT_CALL(observer_,
                 PermissionStateChanged(web_state(), PermissionMicrophone))
         .Times(0);
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
 
     // Initial load.
     test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
@@ -165,7 +165,7 @@
                                         PermissionStateAllowed));
 
     // Initial load.
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
     test::LoadUrl(web_state(), test_server_->GetURL("/microphone_only.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
     ExpectThatLastRequestedPermissionsMatchesPermissions(
@@ -197,7 +197,7 @@
                                         PermissionStateAllowed));
 
     // Initial load.
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
     test::LoadUrl(web_state(),
                   test_server_->GetURL("/camera_and_microphone.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
@@ -224,7 +224,7 @@
                 PermissionStateChanged(web_state(), PermissionCamera))
         .Times(0);
 
-    delegate_.SetShouldGrantPermissions(NO);
+    delegate_.SetPermissionDecision(PermissionDecisionDeny);
     test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
     ExpectThatLastRequestedPermissionsMatchesPermissions(
@@ -239,7 +239,7 @@
 TEST_F(PermissionsInttest,
        TestsThatWebStateShouldNotAlterPermissionIfNotAccessible) {
   if (@available(iOS 15.0, *)) {
-    delegate_.SetShouldGrantPermissions(NO);
+    delegate_.SetPermissionDecision(PermissionDecisionDeny);
     test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
     ExpectThatLastRequestedPermissionsMatchesPermissions(
@@ -272,7 +272,7 @@
 TEST_F(PermissionsInttest, TestsThatPageReloadResetsPermissionState) {
   if (@available(iOS 15.0, *)) {
     // Initial load should allow permission.
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
     test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
     EXPECT_EQ(web_state()->GetStateForPermission(PermissionCamera),
@@ -283,7 +283,7 @@
     // Reload should reset permission. Handler should be called again, and
     // permission state should be NotAccessible.
     delegate_.ClearLastRequestedPermissions();
-    delegate_.SetShouldGrantPermissions(NO);
+    delegate_.SetPermissionDecision(PermissionDecisionDeny);
     web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
                                                 /*check_for_repost=*/false);
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
@@ -301,7 +301,7 @@
 TEST_F(PermissionsInttest, TestsThatWebStateDoesNotPreservePermissionState) {
   if (@available(iOS 15.0, *)) {
     // Initial load should allow permission.
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
     test::LoadUrl(web_state(), test_server_->GetURL("/camera_only.html"));
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
       return web_state()->GetStateForPermission(PermissionCamera) ==
@@ -314,7 +314,7 @@
     // called again, permission state should be NotAccessible and the observer
     // should NOT be invoked.
     delegate_.ClearLastRequestedPermissions();
-    delegate_.SetShouldGrantPermissions(NO);
+    delegate_.SetPermissionDecision(PermissionDecisionDeny);
     test::LoadUrl(web_state(),
                   test_server_->GetURL("/camera_and_microphone.html"));
     SpinRunLoopWithMinDelay(kWaitForPageLoadTimeout);
@@ -335,7 +335,7 @@
        TestsThatMovingBackwardOrForwardResetsPermissionState) {
   if (@available(iOS 15.0, *)) {
     // Initial load for both pages should allow permission.
-    delegate_.SetShouldGrantPermissions(YES);
+    delegate_.SetPermissionDecision(PermissionDecisionGrant);
     test::LoadUrl(web_state(), test_server_->GetURL("/microphone_only.html"));
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
       return web_state()->GetStateForPermission(PermissionMicrophone) ==
@@ -371,7 +371,7 @@
     // WKMediaCaptureStateNone. The two following lines of code should be
     // uncommented when this is fixed.
 
-    // delegate_.SetShouldGrantPermissions(NO);
+    // delegate_.SetPermissionDecision(PermissionDecisionDeny);
     // handler_.decision = WKPermissionDecisionDeny;
     web_state()->GetNavigationManager()->GoBack();
     EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool {
diff --git a/ios/web/web_state/web_state_delegate.mm b/ios/web/web_state/web_state_delegate.mm
index 85ac58c..960ba59 100644
--- a/ios/web/web_state/web_state_delegate.mm
+++ b/ios/web/web_state/web_state_delegate.mm
@@ -46,11 +46,11 @@
   return nullptr;
 }
 
-bool WebStateDelegate::HandlePermissionsDecisionRequest(
+void WebStateDelegate::HandlePermissionsDecisionRequest(
     WebState* source,
     NSArray<NSNumber*>* permissions,
     WebStatePermissionDecisionHandler handler) {
-  return false;
+  handler(PermissionDecisionShowDefaultPrompt);
 }
 
 void WebStateDelegate::OnAuthRequired(WebState* source,
diff --git a/ios/web/web_state/web_state_delegate_bridge.mm b/ios/web/web_state/web_state_delegate_bridge.mm
index e71c526..b58fd3e5 100644
--- a/ios/web/web_state/web_state_delegate_bridge.mm
+++ b/ios/web/web_state/web_state_delegate_bridge.mm
@@ -70,17 +70,18 @@
   return nullptr;
 }
 
-bool WebStateDelegateBridge::HandlePermissionsDecisionRequest(
+void WebStateDelegateBridge::HandlePermissionsDecisionRequest(
     WebState* source,
     NSArray<NSNumber*>* permissions,
     WebStatePermissionDecisionHandler handler) API_AVAILABLE(ios(15.0)) {
   if ([delegate_ respondsToSelector:@selector(webState:
                                         handlePermissions:decisionHandler:)]) {
-    return [delegate_ webState:source
-             handlePermissions:permissions
-               decisionHandler:handler];
+    [delegate_ webState:source
+        handlePermissions:permissions
+          decisionHandler:handler];
+  } else {
+    handler(PermissionDecisionShowDefaultPrompt);
   }
-  return false;
 }
 
 void WebStateDelegateBridge::OnAuthRequired(
diff --git a/ios/web/web_state/web_state_delegate_bridge_unittest.mm b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
index 9f15440..8409079 100644
--- a/ios/web/web_state/web_state_delegate_bridge_unittest.mm
+++ b/ios/web/web_state/web_state_delegate_bridge_unittest.mm
@@ -142,14 +142,13 @@
     __block bool callback_called = false;
     EXPECT_FALSE([delegate_ permissionsRequestHandled]);
     EXPECT_FALSE([delegate_ webState]);
-    bool useHandlerAnswerTheRequest = bridge_->HandlePermissionsDecisionRequest(
-        &fake_web_state_, @[], ^(bool allow) {
-          EXPECT_TRUE(allow);
+    bridge_->HandlePermissionsDecisionRequest(
+        &fake_web_state_, @[], ^(PermissionDecision decision) {
+          EXPECT_EQ(decision, PermissionDecisionGrant);
           callback_called = true;
         });
     EXPECT_TRUE([delegate_ permissionsRequestHandled]);
     EXPECT_EQ(&fake_web_state_, [delegate_ webState]);
-    EXPECT_TRUE(useHandlerAnswerTheRequest);
     EXPECT_TRUE(callback_called);
   }
 }
@@ -160,13 +159,16 @@
        HandlePermissionsDecisionRequestWithNoDelegateMethod) {
   if (@available(iOS 15.0, *)) {
     __block bool callback_called = false;
-    bool useHandlerAnswerTheRequest =
-        empty_delegate_bridge_->HandlePermissionsDecisionRequest(
-            nullptr, @[], ^(bool allow) {
-              callback_called = true;
-            });
-    EXPECT_FALSE(useHandlerAnswerTheRequest);
-    EXPECT_FALSE(callback_called);
+    empty_delegate_bridge_->HandlePermissionsDecisionRequest(
+        nullptr, @[], ^(PermissionDecision decision) {
+          // Default decision `PermissionDecisionShowDefaultPrompt` will be used
+          // when delegate doesn't implement
+          // `webState:handlePermissions:decisionHandler:` method to handle the
+          // permissions.
+          EXPECT_EQ(decision, PermissionDecisionShowDefaultPrompt);
+          callback_called = true;
+        });
+    EXPECT_TRUE(callback_called);
   }
 }
 
diff --git a/ios/web/web_state/web_state_impl_realized_web_state.mm b/ios/web/web_state/web_state_impl_realized_web_state.mm
index bd96aab..4c0fec2 100644
--- a/ios/web/web_state/web_state_impl_realized_web_state.mm
+++ b/ios/web/web_state/web_state_impl_realized_web_state.mm
@@ -851,17 +851,24 @@
 void WebStateImpl::RealizedWebState::RequestPermissionsWithDecisionHandler(
     NSArray<NSNumber*>* permissions,
     PermissionDecisionHandler web_view_decision_handler) {
-  bool delegate_can_handle_decision = false;
   if (delegate_) {
     WebStatePermissionDecisionHandler web_state_decision_handler =
-        ^(BOOL allowed) {
-          allowed ? web_view_decision_handler(WKPermissionDecisionGrant)
-                  : web_view_decision_handler(WKPermissionDecisionDeny);
+        ^(PermissionDecision decision) {
+          switch (decision) {
+            case PermissionDecisionShowDefaultPrompt:
+              web_view_decision_handler(WKPermissionDecisionPrompt);
+              break;
+            case PermissionDecisionGrant:
+              web_view_decision_handler(WKPermissionDecisionGrant);
+              break;
+            case PermissionDecisionDeny:
+              web_view_decision_handler(WKPermissionDecisionDeny);
+              break;
+          }
         };
-    delegate_can_handle_decision = delegate_->HandlePermissionsDecisionRequest(
-        owner_, permissions, web_state_decision_handler);
-  }
-  if (!delegate_can_handle_decision) {
+    delegate_->HandlePermissionsDecisionRequest(owner_, permissions,
+                                                web_state_decision_handler);
+  } else {
     web_view_decision_handler(WKPermissionDecisionPrompt);
   }
 }
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index dd76614..3d3e315 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -582,9 +582,9 @@
   return _javaScriptDialogPresenter.get();
 }
 
-- (BOOL)webState:(web::WebState*)webState
+- (void)webState:(web::WebState*)webState
     handlePermissions:(NSArray<NSNumber*>*)permissions
-      decisionHandler:(void (^)(BOOL allow))decisionHandler
+      decisionHandler:(web::WebStatePermissionDecisionHandler)decisionHandler
     API_AVAILABLE(ios(15.0)) {
   DCHECK(decisionHandler);
   CWVMediaCaptureType mediaCaptureType;
@@ -609,17 +609,22 @@
         requestMediaCapturePermissionForType:mediaCaptureType
                              decisionHandler:^(CWVPermissionDecision decision) {
                                switch (decision) {
+                                 case CWVPermissionDecisionPrompt:
+                                   decisionHandler(
+                                       web::
+                                           PermissionDecisionShowDefaultPrompt);
+                                   break;
                                  case CWVPermissionDecisionGrant:
-                                   decisionHandler(YES);
+                                   decisionHandler(
+                                       web::PermissionDecisionGrant);
                                    break;
                                  case CWVPermissionDecisionDeny:
-                                   decisionHandler(NO);
+                                   decisionHandler(web::PermissionDecisionDeny);
                                    break;
                                }
                              }];
-    return YES;
   } else {
-    return NO;
+    decisionHandler(web::PermissionDecisionShowDefaultPrompt);
   }
 }
 
diff --git a/ios/web_view/public/cwv_ui_delegate.h b/ios/web_view/public/cwv_ui_delegate.h
index 649149c..4bd4841 100644
--- a/ios/web_view/public/cwv_ui_delegate.h
+++ b/ios/web_view/public/cwv_ui_delegate.h
@@ -20,6 +20,7 @@
 @class CWVNavigationAction;
 
 typedef NS_ENUM(NSInteger, CWVPermissionDecision) {
+  CWVPermissionDecisionPrompt,
   CWVPermissionDecisionGrant,
   CWVPermissionDecisionDeny,
 } API_AVAILABLE(ios(15.0));
diff --git a/ios/web_view/shell/BUILD.gn b/ios/web_view/shell/BUILD.gn
index 094472a0..854bfc7 100644
--- a/ios/web_view/shell/BUILD.gn
+++ b/ios/web_view/shell/BUILD.gn
@@ -2,8 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/buildflag_header.gni")
 import("//build/config/ios/ios_sdk.gni")
 import("//build/config/ios/rules.gni")
+import("//ios/web_view/features.gni")
 
 declare_args() {
   # The bundle identifier. Overriding this will affect the provisioning profile
@@ -35,6 +37,11 @@
   ios_web_view_shell_entitlements_path = "//build/config/ios/entitlements.plist"
 }
 
+buildflag_header("buildflags") {
+  header = "buildflags.h"
+  flags = [ "IOS_WEB_VIEW_INCLUDE_CRONET=$ios_web_view_include_cronet" ]
+}
+
 ios_app_bundle("ios_web_view_shell") {
   info_plist = "Info.plist"
 
@@ -54,10 +61,19 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("cwv_framework") {
+  sources = [ "cwv_framework.h" ]
+
+  deps = [
+    ":buildflags",
+    "//ios/web_view:web_view+link",
+  ]
+}
+
 source_set("shell_auth_service_interface") {
   sources = [ "shell_auth_service.h" ]
 
-  deps = [ "//ios/web_view:web_view+link" ]
+  deps = [ ":cwv_framework" ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
 }
@@ -66,8 +82,8 @@
   sources = [ "shell_auth_service_fake.m" ]
 
   deps = [
+    ":cwv_framework",
     ":shell_auth_service_interface",
-    "//ios/web_view:web_view+link",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -76,7 +92,7 @@
 source_set("shell_risk_data_loader_interface") {
   sources = [ "shell_risk_data_loader.h" ]
 
-  deps = [ "//ios/web_view:web_view+link" ]
+  deps = [ ":cwv_framework" ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
 }
@@ -85,8 +101,8 @@
   sources = [ "shell_risk_data_loader_fake.m" ]
 
   deps = [
+    ":cwv_framework",
     ":shell_risk_data_loader_interface",
-    "//ios/web_view:web_view+link",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -96,8 +112,8 @@
   sources = [ "shell_trusted_vault_provider.h" ]
 
   deps = [
+    ":cwv_framework",
     ":shell_auth_service_interface",
-    "//ios/web_view:web_view+link",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -107,8 +123,8 @@
   sources = [ "shell_trusted_vault_provider_fake.m" ]
 
   deps = [
+    ":cwv_framework",
     ":shell_trusted_vault_provider_interface",
-    "//ios/web_view:web_view+link",
   ]
 
   configs += [ "//build/config/compiler:enable_arc" ]
@@ -128,12 +144,12 @@
   ]
 
   deps = [
+    ":cwv_framework",
     ":shell_auth_service_interface",
     ":shell_risk_data_loader_interface",
     ":shell_trusted_vault_provider_interface",
     "//base",
     "//ios/third_party/webkit",
-    "//ios/web_view:web_view+link",
     ios_web_view_shell_auth_service,
     ios_web_view_shell_risk_data_loader,
     ios_web_view_shell_trusted_vault_provider,
diff --git a/ios/web_view/shell/cwv_framework.h b/ios/web_view/shell/cwv_framework.h
new file mode 100644
index 0000000..afe0ba4
--- /dev/null
+++ b/ios/web_view/shell/cwv_framework.h
@@ -0,0 +1,16 @@
+// 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_WEB_VIEW_SHELL_CWV_FRAMEWORK_H_
+#define IOS_WEB_VIEW_SHELL_CWV_FRAMEWORK_H_
+
+#import "ios/web_view/shell/buildflags.h"
+
+#if BUILDFLAG(IOS_WEB_VIEW_INCLUDE_CRONET)
+#import <CronetChromeWebView/CronetChromeWebView.h>
+#else
+#import <ChromeWebView/ChromeWebView.h>
+#endif
+
+#endif  // IOS_WEB_VIEW_SHELL_CWV_FRAMEWORK_H_
diff --git a/ios/web_view/shell/shell_auth_service.h b/ios/web_view/shell/shell_auth_service.h
index 23587bd..0e7a5d1 100644
--- a/ios/web_view/shell/shell_auth_service.h
+++ b/ios/web_view/shell/shell_auth_service.h
@@ -4,7 +4,7 @@
 #ifndef IOS_WEB_VIEW_SHELL_SHELL_AUTH_SERVICE_H_
 #define IOS_WEB_VIEW_SHELL_SHELL_AUTH_SERVICE_H_
 
-#import <ChromeWebView/ChromeWebView.h>
+#import "ios/web_view/shell/cwv_framework.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
diff --git a/ios/web_view/shell/shell_autofill_delegate.h b/ios/web_view/shell/shell_autofill_delegate.h
index 33bc4cc..0a617a6 100644
--- a/ios/web_view/shell/shell_autofill_delegate.h
+++ b/ios/web_view/shell/shell_autofill_delegate.h
@@ -5,8 +5,8 @@
 #ifndef IOS_WEB_VIEW_SHELL_SHELL_AUTOFILL_DELEGATE_H_
 #define IOS_WEB_VIEW_SHELL_SHELL_AUTOFILL_DELEGATE_H_
 
-#import <ChromeWebView/ChromeWebView.h>
 #import <Foundation/Foundation.h>
+#import "ios/web_view/shell/cwv_framework.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
diff --git a/ios/web_view/shell/shell_translation_delegate.h b/ios/web_view/shell/shell_translation_delegate.h
index 45be1c9..2bd57a1 100644
--- a/ios/web_view/shell/shell_translation_delegate.h
+++ b/ios/web_view/shell/shell_translation_delegate.h
@@ -5,7 +5,8 @@
 #ifndef IOS_WEB_VIEW_SHELL_SHELL_TRANSLATION_DELEGATE_H_
 #define IOS_WEB_VIEW_SHELL_SHELL_TRANSLATION_DELEGATE_H_
 
-#import <ChromeWebView/ChromeWebView.h>
+#import "ios/web_view/shell/cwv_framework.h"
+
 #import <Foundation/Foundation.h>
 
 NS_ASSUME_NONNULL_BEGIN
diff --git a/ios/web_view/shell/shell_trusted_vault_provider.h b/ios/web_view/shell/shell_trusted_vault_provider.h
index 8b24d3d..36aaa88e 100644
--- a/ios/web_view/shell/shell_trusted_vault_provider.h
+++ b/ios/web_view/shell/shell_trusted_vault_provider.h
@@ -5,7 +5,7 @@
 #ifndef IOS_WEB_VIEW_SHELL_SHELL_TRUSTED_VAULT_PROVIDER_H_
 #define IOS_WEB_VIEW_SHELL_SHELL_TRUSTED_VAULT_PROVIDER_H_
 
-#import <ChromeWebView/ChromeWebView.h>
+#import "ios/web_view/shell/cwv_framework.h"
 
 #import "ios/web_view/shell/shell_auth_service.h"
 
diff --git a/ios/web_view/shell/shell_view_controller.h b/ios/web_view/shell/shell_view_controller.h
index 1a380921..f4fa8cd 100644
--- a/ios/web_view/shell/shell_view_controller.h
+++ b/ios/web_view/shell/shell_view_controller.h
@@ -5,7 +5,8 @@
 #ifndef IOS_WEB_VIEW_SHELL_SHELL_VIEW_CONTROLLER_H_
 #define IOS_WEB_VIEW_SHELL_SHELL_VIEW_CONTROLLER_H_
 
-#import <ChromeWebView/ChromeWebView.h>
+#import "ios/web_view/shell/cwv_framework.h"
+
 #import <UIKit/UIKit.h>
 
 NS_ASSUME_NONNULL_BEGIN
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index 5d00996..8f5ee7b 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -1160,6 +1160,13 @@
 
   [alertController
       addAction:[UIAlertAction
+                    actionWithTitle:@"Show Default Prompt"
+                              style:UIAlertActionStyleDefault
+                            handler:^(UIAlertAction* action) {
+                              decisionHandler(CWVPermissionDecisionPrompt);
+                            }]];
+  [alertController
+      addAction:[UIAlertAction
                     actionWithTitle:@"Grant"
                               style:UIAlertActionStyleDefault
                             handler:^(UIAlertAction* action) {
diff --git a/net/base/network_interfaces_linux.cc b/net/base/network_interfaces_linux.cc
index ff43b4e..e4f3ec5 100644
--- a/net/base/network_interfaces_linux.cc
+++ b/net/base/network_interfaces_linux.cc
@@ -18,6 +18,7 @@
 #include <sys/ioctl.h>
 #include <sys/types.h>
 
+#include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/scoped_file.h"
 #include "base/strings/escape.h"
@@ -26,10 +27,13 @@
 #include "base/strings/string_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "net/base/address_map_linux.h"
 #include "net/base/address_tracker_linux.h"
+#include "net/base/features.h"
 #include "net/base/ip_endpoint.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_interfaces_posix.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
 #if BUILDFLAG(IS_ANDROID)
@@ -238,13 +242,27 @@
       network.type = internal::GetInterfaceConnectionType(network.name);
     return ret;
   }
-#endif
+#endif  // BUILDFLAG(IS_ANDROID)
 
-  internal::AddressTrackerLinux tracker;
-  tracker.Init();
+  const AddressMapOwnerLinux* map_owner = nullptr;
+  absl::optional<internal::AddressTrackerLinux> temp_tracker;
+#if BUILDFLAG(IS_LINUX)
+  // If NetworkChangeNotifier already maintains a map owner in this process, use
+  // it.
+  if (base::FeatureList::IsEnabled(features::kAddressTrackerLinuxIsProxied)) {
+    map_owner = NetworkChangeNotifier::GetAddressMapOwner();
+  }
+#endif  // BUILDFLAG(IS_LINUX)
+  if (!map_owner) {
+    // If there is no existing map_owner, create an AdressTrackerLinux and
+    // initialize it.
+    temp_tracker.emplace();
+    temp_tracker->Init();
+    map_owner = &temp_tracker.value();
+  }
 
   return internal::GetNetworkListImpl(
-      networks, policy, tracker.GetOnlineLinks(), tracker.GetAddressMap(),
+      networks, policy, map_owner->GetOnlineLinks(), map_owner->GetAddressMap(),
       &internal::AddressTrackerLinux::GetInterfaceName);
 }
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index c304ef0..133908386 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -9696,6 +9696,21 @@
             ]
         }
     ],
+    "PasswordNotesWithBackup": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PasswordNotesWithBackup"
+                    ]
+                }
+            ]
+        }
+    ],
     "PasswordsGrouping": [
         {
             "platforms": [
@@ -14458,6 +14473,22 @@
             ]
         }
     ],
+    "WebRTC-RtcEventLogNewFormat": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows",
+                "chromeos",
+                "chromeos_lacros"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled"
+                }
+            ]
+        }
+    ],
     "WebRTC-RttMult": [
         {
             "platforms": [
diff --git a/third_party/blink/common/BUILD.gn b/third_party/blink/common/BUILD.gn
index b993939e..1ea03285 100644
--- a/third_party/blink/common/BUILD.gn
+++ b/third_party/blink/common/BUILD.gn
@@ -326,6 +326,7 @@
     "//ui/display",
     "//ui/display/mojom",
     "//ui/events:events_base",
+    "//ui/events/mojom:event_latency_metadata_mojom",
     "//ui/latency/mojom:shared_mojom_traits",
   ]
 
diff --git a/third_party/blink/common/DEPS b/third_party/blink/common/DEPS
index 40bf560cf..77997ecf2 100644
--- a/third_party/blink/common/DEPS
+++ b/third_party/blink/common/DEPS
@@ -41,6 +41,7 @@
     "+ui/base/dragdrop/mojom",
     "+ui/display",
     "+ui/events/base_event_utils.h",
+    "+ui/events/mojom/event_latency_metadata_mojom_traits.h",
     "+ui/gfx/presentation_feedback.h",
     "+ui/gfx/transform.h",
     "+ui/gfx/geometry",
diff --git a/third_party/blink/common/input/web_coalesced_input_event_mojom_traits.cc b/third_party/blink/common/input/web_coalesced_input_event_mojom_traits.cc
index cae8aad..1adbb11 100644
--- a/third_party/blink/common/input/web_coalesced_input_event_mojom_traits.cc
+++ b/third_party/blink/common/input/web_coalesced_input_event_mojom_traits.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/public/common/input/web_gesture_event.h"
 #include "third_party/blink/public/common/input/web_keyboard_event.h"
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
+#include "ui/events/mojom/event_latency_metadata_mojom_traits.h"
 #include "ui/latency/mojom/latency_info_mojom_traits.h"
 
 namespace mojo {
@@ -361,6 +362,12 @@
     return false;
   }
 
+  ui::EventLatencyMetadata event_latency_metadata;
+  if (!event.ReadEventLatencyMetadata(&event_latency_metadata)) {
+    return false;
+  };
+  input_event->GetModifiableEventLatencyMetadata() =
+      std::move(event_latency_metadata);
   ui::LatencyInfo latency_info;
   if (!event.ReadLatency(&latency_info))
     return false;
diff --git a/third_party/blink/public/common/DEPS b/third_party/blink/public/common/DEPS
index c2c3018..74484c759 100644
--- a/third_party/blink/public/common/DEPS
+++ b/third_party/blink/public/common/DEPS
@@ -36,5 +36,6 @@
     "+ui/gfx/display_color_spaces.h",
     "+ui/gfx/geometry",
     "+ui/latency/latency_info.h",
+    "+ui/events/event_latency_metadata.h",
     "+url",
 ]
diff --git a/third_party/blink/public/common/input/web_coalesced_input_event_mojom_traits.h b/third_party/blink/public/common/input/web_coalesced_input_event_mojom_traits.h
index aa7f806..31edf22 100644
--- a/third_party/blink/public/common/input/web_coalesced_input_event_mojom_traits.h
+++ b/third_party/blink/public/common/input/web_coalesced_input_event_mojom_traits.h
@@ -7,6 +7,7 @@
 
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/common/input/web_coalesced_input_event.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/mojom/input/input_handler.mojom.h"
 
 namespace mojo {
@@ -35,6 +36,11 @@
     return event->latency_info();
   }
 
+  static const ui::EventLatencyMetadata& event_latency_metadata(
+      const std::unique_ptr<blink::WebCoalescedInputEvent>& event) {
+    return event->Event().GetEventLatencyMetadata();
+  }
+
   static blink::mojom::KeyDataPtr key_data(
       const std::unique_ptr<blink::WebCoalescedInputEvent>& event);
   static blink::mojom::PointerDataPtr pointer_data(
diff --git a/third_party/blink/public/common/input/web_input_event.h b/third_party/blink/public/common/input/web_input_event.h
index d6ee25c8..7fe9ec3 100644
--- a/third_party/blink/public/common/input/web_input_event.h
+++ b/third_party/blink/public/common/input/web_input_event.h
@@ -40,6 +40,7 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/common_export.h"
 #include "third_party/blink/public/mojom/input/input_event.mojom-shared.h"
+#include "ui/events/event_latency_metadata.h"
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/point_f.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -314,6 +315,13 @@
   base::TimeTicks TimeStamp() const { return time_stamp_; }
   void SetTimeStamp(base::TimeTicks time_stamp) { time_stamp_ = time_stamp; }
 
+  const ui::EventLatencyMetadata& GetEventLatencyMetadata() const {
+    return event_latency_metadata_;
+  }
+  ui::EventLatencyMetadata& GetModifiableEventLatencyMetadata() {
+    return event_latency_metadata_;
+  }
+
   void SetTargetFrameMovedRecently() {
     modifiers_ |= kTargetFrameMovedRecently;
   }
@@ -357,6 +365,8 @@
   base::TimeTicks time_stamp_;
   Type type_ = Type::kUndefined;
   int modifiers_ = kNoModifiers;
+
+  ui::EventLatencyMetadata event_latency_metadata_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 88fd5ab..12439f3 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -298,6 +298,7 @@
     "//ui/base/mojom",
     "//ui/display/mojom",
     "//ui/events/mojom",
+    "//ui/events/mojom:event_latency_metadata_mojom",
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
     "//ui/gfx/range/mojom",
diff --git a/third_party/blink/public/mojom/input/input_handler.mojom b/third_party/blink/public/mojom/input/input_handler.mojom
index 020807b..7e289ec 100644
--- a/third_party/blink/public/mojom/input/input_handler.mojom
+++ b/third_party/blink/public/mojom/input/input_handler.mojom
@@ -4,27 +4,28 @@
 
 module blink.mojom;
 
+import "cc/mojom/browser_controls_state.mojom";
 import "cc/mojom/overscroll_behavior.mojom";
 import "cc/mojom/touch_action.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "third_party/blink/public/mojom/input/gesture_event.mojom";
-import "third_party/blink/public/mojom/input/input_event.mojom";
+import "third_party/blink/public/mojom/input/handwriting_gesture_result.mojom";
 import "third_party/blink/public/mojom/input/input_event_result.mojom";
+import "third_party/blink/public/mojom/input/input_event.mojom";
 import "third_party/blink/public/mojom/input/pointer_lock_context.mojom";
 import "third_party/blink/public/mojom/input/pointer_lock_result.mojom";
+import "third_party/blink/public/mojom/input/stylus_writing_gesture.mojom";
 import "third_party/blink/public/mojom/input/touch_event.mojom";
 import "third_party/blink/public/mojom/selection_menu/selection_menu_behavior.mojom";
 import "ui/base/ime/mojom/ime_types.mojom";
-import "third_party/blink/public/mojom/input/handwriting_gesture_result.mojom";
-import "third_party/blink/public/mojom/input/stylus_writing_gesture.mojom";
-import "ui/events/mojom/event.mojom";
 import "ui/events/mojom/event_constants.mojom";
+import "ui/events/mojom/event_latency_metadata.mojom";
+import "ui/events/mojom/event.mojom";
 import "ui/events/mojom/scroll_granularity.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/range/mojom/range.mojom";
 import "ui/latency/mojom/latency_info.mojom";
-import "cc/mojom/browser_controls_state.mojom";
 
 [EnableIf=is_android]
 import "third_party/blink/public/mojom/input/synchronous_compositor.mojom";
@@ -169,6 +170,7 @@
   int32 modifiers;
   mojo_base.mojom.TimeTicks timestamp;
   ui.mojom.LatencyInfo latency;
+  ui.mojom.EventLatencyMetadata event_latency_metadata;
   KeyData? key_data;
   PointerData? pointer_data;
   GestureData? gesture_data;
diff --git a/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom b/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
index a9cf42ec..ae63b31 100644
--- a/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
+++ b/third_party/blink/public/mojom/speculation_rules/speculation_rules.mojom
@@ -56,6 +56,14 @@
   kEager,
 };
 
+// Specifies the v8 world where the JavaScript runs if the JavaScript injects
+// the speculation rule.
+enum SpeculationInjectionWorld {
+  kNone,  // No v8 context, i.e. statically inserted script tags.
+  kMain,
+  kIsolated,
+};
+
 // A single candidate: a URL, an action, a referrer, and any associated
 // metadata that might be needed to make a decision.
 // https://wicg.github.io/nav-speculation/speculation-rules.html#prefetch-candidate
@@ -86,4 +94,8 @@
   // The expected No-Vary-Search header if specified.
   // Explainer: https://github.com/WICG/nav-speculation/blob/main/no-vary-search.md#a-referrer-hint
   network.mojom.NoVarySearch? no_vary_search_expected;
+
+  // The v8 world where the JavaScript runs if the speculation rule is injected
+  // by the JavaScript.
+  SpeculationInjectionWorld injection_world = kNone;
 };
diff --git a/third_party/blink/renderer/core/animation/animation.cc b/third_party/blink/renderer/core/animation/animation.cc
index dd0ddb0..1b23fb1b 100644
--- a/third_party/blink/renderer/core/animation/animation.cc
+++ b/third_party/blink/renderer/core/animation/animation.cc
@@ -941,8 +941,9 @@
 
   // Update content timing to be based on new timeline type. This ensures that
   // EffectEnd() is returning a value appropriate to the new timeline.
-  if (content_ && timeline_)
+  if (content_) {
     content_->InvalidateNormalizedTiming();
+  }
 
   reset_current_time_on_resume_ = false;
 
@@ -952,39 +953,37 @@
     keyframe_effect->Model()->SetViewTimelineIfRequired(view_timeline);
   }
 
-  if (timeline) {
-    if (!timeline->IsMonotonicallyIncreasing()) {
-      ApplyPendingPlaybackRate();
-      AnimationTimeDelta boundary_time =
-          (playback_rate_ > 0) ? AnimationTimeDelta() : EffectEnd();
-      switch (old_play_state) {
-        case kIdle:
-          break;
+  if (timeline && !timeline->IsMonotonicallyIncreasing()) {
+    ApplyPendingPlaybackRate();
+    AnimationTimeDelta boundary_time =
+        (playback_rate_ > 0) ? AnimationTimeDelta() : EffectEnd();
+    switch (old_play_state) {
+      case kIdle:
+        break;
 
-        case kRunning:
-        case kFinished:
-          // A non-monotonic timeline has a fixed start time at the beginning or
-          // end of the timeline.
+      case kRunning:
+      case kFinished:
+        // A non-monotonic timeline has a fixed start time at the beginning or
+        // end of the timeline.
+        start_time_ = boundary_time;
+        break;
+
+      case kPaused:
+        if (old_current_time) {
+          reset_current_time_on_resume_ = true;
+          start_time_ = absl::nullopt;
+          hold_time_ = progress * EffectEnd();
+        } else if (PendingInternal()) {
           start_time_ = boundary_time;
-          break;
+        }
+        break;
 
-        case kPaused:
-          if (old_current_time) {
-            reset_current_time_on_resume_ = true;
-            start_time_ = absl::nullopt;
-            hold_time_ = progress * EffectEnd();
-          } else if (PendingInternal()) {
-            start_time_ = boundary_time;
-          }
-          break;
-
-        default:
-          NOTREACHED();
-      }
-    } else if (old_current_time && old_timeline &&
-               !old_timeline->IsMonotonicallyIncreasing()) {
-      SetCurrentTimeInternal(progress * EffectEnd());
+      default:
+        NOTREACHED();
     }
+  } else if (old_current_time && old_timeline &&
+             !old_timeline->IsMonotonicallyIncreasing()) {
+    SetCurrentTimeInternal(progress * EffectEnd());
   }
 
   // 4. If the start time of animation is resolved, make the animation’s hold
@@ -2293,19 +2292,32 @@
 }
 
 void Animation::OnRangeUpdate() {
+  // Change in animation range has no effect unless using a scroll-timeline.
+  ScrollTimeline* scroll_timeline = DynamicTo<ScrollTimeline>(timeline_.Get());
+  if (!scroll_timeline) {
+    return;
+  }
+
   SetOutdated();
   if (content_) {
     // Animation range affects intrinsic iteration duration, which in turn
     // affects iteration duration in normalized timing.
     content_->InvalidateNormalizedTiming();
+    content_->Invalidate();
   }
   if (start_time_) {
     UpdateStartTimeForViewTimeline();
   }
 
+  Update(kTimingUpdateOnDemand);
+
+  // Clamp current time to end time if finished. The |previous_current_time_|
+  // flag prevents current time from jumping when updating the finished state
+  // on an animation and not performing an explicit seek operation.
+  previous_current_time_ = absl::nullopt;
   UpdateFinishedState(UpdateType::kContinuous, NotificationType::kAsync);
 
-  SetCompositorPending(false);
+  SetCompositorPending(/*effect_changed=*/true);
 
   // Inform devtools of a potential change to the play state.
   NotifyProbe();
diff --git a/third_party/blink/renderer/core/animation/animation.h b/third_party/blink/renderer/core/animation/animation.h
index e764e9ee..03af6ee 100644
--- a/third_party/blink/renderer/core/animation/animation.h
+++ b/third_party/blink/renderer/core/animation/animation.h
@@ -233,6 +233,14 @@
       OnRangeUpdate();
     }
   }
+  virtual void SetRange(const absl::optional<TimelineOffset>& range_start,
+                        const absl::optional<TimelineOffset>& range_end) {
+    if (range_start_ != range_start || range_end_ != range_end) {
+      range_start_ = range_start;
+      range_end_ = range_end;
+      OnRangeUpdate();
+    }
+  }
 
   void OnRangeUpdate();
 
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index 77d45b6c..b90b490 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -65,6 +65,16 @@
   InvalidateNormalizedTiming();
 }
 
+AnimationTimeDelta AnimationEffect::IntrinsicIterationDuration() const {
+  if (auto* animation = GetAnimation()) {
+    auto* timeline = animation->timeline();
+    if (timeline) {
+      return timeline->CalculateIntrinsicIterationDuration(animation, timing_);
+    }
+  }
+  return AnimationTimeDelta();
+}
+
 // Scales all timing values so that end_time == timeline_duration
 // https://drafts.csswg.org/web-animations-2/#time-based-animation-to-a-proportional-animation
 void AnimationEffect::EnsureNormalizedTiming() const {
diff --git a/third_party/blink/renderer/core/animation/animation_effect.h b/third_party/blink/renderer/core/animation/animation_effect.h
index c538b15..950238a 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.h
+++ b/third_party/blink/renderer/core/animation/animation_effect.h
@@ -168,9 +168,7 @@
   // In web-animations-1, auto is treated as "the value zero for the purpose of
   // timing model calculations and for the result of the duration member
   // returned from getComputedTiming()".
-  virtual AnimationTimeDelta IntrinsicIterationDuration() const {
-    return AnimationTimeDelta();
-  }
+  virtual AnimationTimeDelta IntrinsicIterationDuration() const;
 
   virtual AnimationTimeDelta CalculateTimeToEffectChange(
       bool forwards,
diff --git a/third_party/blink/renderer/core/animation/animation_test_helpers.h b/third_party/blink/renderer/core/animation/animation_test_helpers.h
index 1140b59..3f15044 100644
--- a/third_party/blink/renderer/core/animation/animation_test_helpers.h
+++ b/third_party/blink/renderer/core/animation/animation_test_helpers.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ANIMATION_ANIMATION_TEST_HELPERS_H_
 
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
+#include "third_party/blink/renderer/core/animation/inert_effect.h"
 #include "third_party/blink/renderer/core/animation/interpolation.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -48,6 +49,28 @@
 // InvalidatableInterpolation.
 void EnsureInterpolatedValueCached(ActiveInterpolations*, Document&, Element*);
 
+class TestAnimationProxy : public AnimationProxy {
+ public:
+  // AnimationProxy interface.
+  bool AtScrollTimelineBoundary() const override { return false; }
+  absl::optional<AnimationTimeDelta> TimelineDuration() const override {
+    return absl::nullopt;
+  }
+  AnimationTimeDelta IntrinsicIterationDuration() const override {
+    return AnimationTimeDelta();
+  }
+  double PlaybackRate() const override { return playback_rate_; }
+  bool Paused() const override { return false; }
+  absl::optional<AnimationTimeDelta> InheritedTime() const override {
+    return AnimationTimeDelta();
+  }
+
+  void SetPlaybackRate(double rate) { playback_rate_ = rate; }
+
+ private:
+  double playback_rate_ = 1;
+};
+
 }  // namespace animation_test_helpers
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/core/animation/animation_timeline.h b/third_party/blink/renderer/core/animation/animation_timeline.h
index 42cdd0f8..579a988 100644
--- a/third_party/blink/renderer/core/animation/animation_timeline.h
+++ b/third_party/blink/renderer/core/animation/animation_timeline.h
@@ -72,6 +72,14 @@
   // Changing scroll-linked animation start_time initialization is under
   // consideration here: https://github.com/w3c/csswg-drafts/issues/2075.
   virtual absl::optional<base::TimeDelta> InitialStartTimeForAnimations() = 0;
+
+  virtual AnimationTimeDelta CalculateIntrinsicIterationDuration(
+      const absl::optional<TimelineOffset>& rangeStart,
+      const absl::optional<TimelineOffset>& rangeEnd,
+      const Timing&) {
+    return AnimationTimeDelta();
+  }
+
   virtual AnimationTimeDelta CalculateIntrinsicIterationDuration(
       const Animation*,
       const Timing&) {
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.cc b/third_party/blink/renderer/core/animation/css/css_animation.cc
index 942a5d2..3d49efa 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animation.cc
@@ -70,6 +70,20 @@
   ignore_css_range_end_ = true;
 }
 
+void CSSAnimation::SetRange(const absl::optional<TimelineOffset>& range_start,
+                            const absl::optional<TimelineOffset>& range_end) {
+  if (GetIgnoreCSSRangeStart() && GetIgnoreCSSRangeEnd()) {
+    return;
+  }
+
+  const absl::optional<TimelineOffset>& adjusted_range_start =
+      GetIgnoreCSSRangeStart() ? GetRangeStartInternal() : range_start;
+  const absl::optional<TimelineOffset>& adjusted_range_end =
+      GetIgnoreCSSRangeEnd() ? GetRangeEndInternal() : range_end;
+
+  Animation::SetRange(adjusted_range_start, adjusted_range_end);
+}
+
 void CSSAnimation::setStartTime(const V8CSSNumberish* start_time,
                                 ExceptionState& exception_state) {
   PlayStateTransitionScope scope(*this);
diff --git a/third_party/blink/renderer/core/animation/css/css_animation.h b/third_party/blink/renderer/core/animation/css/css_animation.h
index 36ce2df..5954b71 100644
--- a/third_party/blink/renderer/core/animation/css/css_animation.h
+++ b/third_party/blink/renderer/core/animation/css/css_animation.h
@@ -58,6 +58,12 @@
   void setRangeEnd(const RangeBoundary* range_end,
                    ExceptionState& exception_state) override;
 
+  // Conditionally updates both boundaries of the animation range.
+  // If the corresponding boundary has been explicitly set via WAAPI
+  // the new value will be ignored.
+  void SetRange(const absl::optional<TimelineOffset>& range_start,
+                const absl::optional<TimelineOffset>& range_end) override;
+
   // When set, subsequent changes to animation-<property> no longer affect
   // <property>.
   // https://drafts.csswg.org/css-animations-2/#interaction-between-animation-play-state-and-web-animations-API
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 b0efe43..a9d56e2 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -97,6 +97,222 @@
 
 namespace {
 
+class CSSAnimationProxy : public AnimationProxy {
+ public:
+  CSSAnimationProxy(AnimationTimeline* timeline,
+                    CSSAnimation* animation,
+                    bool is_paused,
+                    const absl::optional<TimelineOffset>& range_start,
+                    const absl::optional<TimelineOffset>& range_end,
+                    const Timing& timing);
+
+  // AnimationProxy interface.
+  bool AtScrollTimelineBoundary() const override {
+    return at_scroll_timeline_boundary_;
+  }
+  absl::optional<AnimationTimeDelta> TimelineDuration() const override {
+    return timeline_duration_;
+  }
+  AnimationTimeDelta IntrinsicIterationDuration() const override {
+    return intrinsic_iteration_duration_;
+  }
+  double PlaybackRate() const override { return playback_rate_; }
+  bool Paused() const override { return is_paused_; }
+  absl::optional<AnimationTimeDelta> InheritedTime() const override {
+    return inherited_time_;
+  }
+
+ private:
+  absl::optional<AnimationTimeDelta> CalculateInheritedTime(
+      AnimationTimeline* timeline,
+      CSSAnimation* animation,
+      const absl::optional<TimelineOffset>& range_start,
+      const absl::optional<TimelineOffset>& range_end,
+      const Timing& timing);
+
+  double playback_rate_ = 1;
+  absl::optional<AnimationTimeDelta> inherited_time_;
+  AnimationTimeDelta intrinsic_iteration_duration_;
+  absl::optional<AnimationTimeDelta> timeline_duration_;
+  bool is_paused_;
+  bool at_scroll_timeline_boundary_ = false;
+};
+
+CSSAnimationProxy::CSSAnimationProxy(
+    AnimationTimeline* timeline,
+    CSSAnimation* animation,
+    bool is_paused,
+    const absl::optional<TimelineOffset>& range_start,
+    const absl::optional<TimelineOffset>& range_end,
+    const Timing& timing)
+    : is_paused_(is_paused) {
+  absl::optional<TimelineOffset> adjusted_range_start;
+  absl::optional<TimelineOffset> adjusted_range_end;
+  if (animation) {
+    playback_rate_ = animation->playbackRate();
+    adjusted_range_start = animation->GetIgnoreCSSRangeStart()
+                               ? animation->GetRangeStartInternal()
+                               : range_start;
+    adjusted_range_end = animation->GetIgnoreCSSRangeEnd()
+                             ? animation->GetRangeEndInternal()
+                             : range_end;
+  } else {
+    adjusted_range_start = range_start;
+    adjusted_range_end = range_end;
+  }
+
+  intrinsic_iteration_duration_ =
+      timeline ? timeline->CalculateIntrinsicIterationDuration(
+                     adjusted_range_start, adjusted_range_end, timing)
+               : AnimationTimeDelta();
+
+  inherited_time_ = CalculateInheritedTime(
+      timeline, animation, adjusted_range_start, adjusted_range_end, timing);
+
+  timeline_duration_ = timeline ? timeline->GetDuration() : absl::nullopt;
+  if (timeline && timeline->IsScrollTimeline() && timeline->CurrentTime()) {
+    AnimationTimeDelta timeline_time = timeline->CurrentTime().value();
+    at_scroll_timeline_boundary_ =
+        timeline_time.is_zero() ||
+        IsWithinAnimationTimeTolerance(timeline_time,
+                                       timeline_duration_.value());
+  }
+}
+
+absl::optional<AnimationTimeDelta> CSSAnimationProxy::CalculateInheritedTime(
+    AnimationTimeline* timeline,
+    CSSAnimation* animation,
+    const absl::optional<TimelineOffset>& range_start,
+    const absl::optional<TimelineOffset>& range_end,
+    const Timing& timing) {
+  absl::optional<AnimationTimeDelta> inherited_time;
+
+  AnimationTimeline* previous_timeline = nullptr;
+  bool resets_current_time_on_resume = false;
+
+  if (animation) {
+    // A cancelled CSS animation does not become active again due to an
+    // animation update.
+    if (!animation->UnlimitedCurrentTime() && !animation->StartTimeInternal()) {
+      return absl::nullopt;
+    }
+
+    // In most cases, current time is preserved on an animation update.
+    inherited_time = animation->UnlimitedCurrentTime();
+    previous_timeline = animation->timeline();
+    resets_current_time_on_resume = animation->ResetsCurrentTimeOnResume();
+  }
+
+  bool range_changed =
+      !animation || (range_start != animation->GetRangeStartInternal() ||
+                     range_end != animation->GetRangeEndInternal());
+
+  if (timeline && timeline->IsScrollTimeline()) {
+    if (is_paused_ || ((timeline == previous_timeline) &&
+                       !resets_current_time_on_resume && !range_changed)) {
+      // Current time is unaffected by the update.
+      return inherited_time;
+    }
+
+    // Running animation with an update that potentially affects the
+    // animation's start time. Need to compute a new value for
+    // inherited_time_.
+    double relative_offset;
+    if (timeline->IsViewTimeline()) {
+      // 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())
+                        : 0;
+      } else {
+        relative_offset =
+            range_end ? DynamicTo<ViewTimeline>(timeline)->ToFractionalOffset(
+                            range_end.value())
+                      : 1;
+      }
+    } else {
+      // A non-view scroll-timeline has its start time at 0 or end time.
+      // TODO(kevers): Update once non-view scroll-timeline support animation
+      // ranges.
+      relative_offset = playback_rate_ >= 0 ? 0 : 1;
+    }
+    if (timeline->CurrentTime()) {
+      AnimationTimeDelta pending_start_time =
+          timeline->GetDuration().value() * relative_offset;
+      return (timeline->CurrentTime().value() - pending_start_time) *
+             playback_rate_;
+    }
+    return absl::nullopt;
+  }
+
+  if (previous_timeline && previous_timeline->IsScrollTimeline() &&
+      previous_timeline->CurrentTime()) {
+    // Going from a scroll timeline to a document or null timeline.
+    // In this case, we preserve the current time.
+    double progress = previous_timeline->CurrentTime().value() /
+                      previous_timeline->GetDuration().value();
+
+    AnimationTimeDelta end_time = std::max(
+        timing.start_delay.AsTimeValue() +
+            MultiplyZeroAlwaysGivesZero(
+                timing.iteration_duration.value_or(AnimationTimeDelta()),
+                timing.iteration_count) +
+            timing.end_delay.AsTimeValue(),
+        AnimationTimeDelta());
+
+    return progress * end_time;
+  }
+
+  if (!timeline) {
+    // If changing from a monotonic-timeline to a null-timeline, current time
+    // may become null.
+    // TODO(https://github.com/w3c/csswg-drafts/issues/6412): Update once the
+    // issue is resolved.
+    if (previous_timeline && previous_timeline->IsMonotonicallyIncreasing() &&
+        !is_paused_ && animation->StartTimeInternal() &&
+        animation->CalculateAnimationPlayState() == Animation::kRunning) {
+      return absl::nullopt;
+    }
+    // A new animation with a null timeline will be stuck in the play or pause
+    // pending state.
+    if (!inherited_time && !animation) {
+      return AnimationTimeDelta();
+    }
+  }
+
+  // A timeline attached to a monotonic timeline that does not currently have a
+  // time will start in either the play or paused state.
+  if (timeline && timeline->IsMonotonicallyIncreasing() && !inherited_time) {
+    return AnimationTimeDelta();
+  }
+
+  return inherited_time;
+}
+
+class CSSTransitionProxy : public AnimationProxy {
+ public:
+  explicit CSSTransitionProxy(absl::optional<AnimationTimeDelta> current_time)
+      : current_time_(current_time) {}
+
+  // AnimationProxy interface.
+  bool AtScrollTimelineBoundary() const override { return false; }
+  absl::optional<AnimationTimeDelta> TimelineDuration() const override {
+    return absl::nullopt;
+  }
+  AnimationTimeDelta IntrinsicIterationDuration() const override {
+    return AnimationTimeDelta();
+  }
+  double PlaybackRate() const override { return 1; }
+  bool Paused() const override { return false; }
+  absl::optional<AnimationTimeDelta> InheritedTime() const override {
+    return current_time_;
+  }
+
+ private:
+  absl::optional<AnimationTimeDelta> current_time_;
+};
+
 // 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
@@ -1483,6 +1699,8 @@
               // kUnset and kPending.
               NOTREACHED();
           }
+        } else if (!animation->GetIgnoreCSSPlayState()) {
+          will_be_playing = !is_paused && play_state != Animation::kIdle;
         } else {
           will_be_playing = (play_state == Animation::kRunning) ||
                             (play_state == Animation::kFinished);
@@ -1507,46 +1725,10 @@
             is_paused != was_paused || logical_property_mapping_change ||
             timeline != existing_animation->Timeline() || range_changed) {
           DCHECK(!is_animation_style_change);
-          absl::optional<AnimationTimeDelta> inherited_time;
-          absl::optional<AnimationTimeDelta> timeline_duration;
 
-          if (timeline) {
-            inherited_time = animation->UnlimitedCurrentTime();
-            timeline_duration = timeline->GetDuration();
-
-            if (will_be_playing &&
-                ((timeline != existing_animation->Timeline()) ||
-                 animation->ResetsCurrentTimeOnResume())) {
-              if (timeline->IsScrollTimeline()) {
-                inherited_time = timeline->CurrentTime();
-              } else {
-                AnimationTimeline* previous_timeline =
-                    existing_animation->Timeline();
-                // Check to see if we are switching from a scroll timeline to a
-                // document timeline.
-                if (previous_timeline &&
-                    previous_timeline->IsScrollTimeline() &&
-                    previous_timeline->CurrentTime()) {
-                  // We need to maintain current progress in the animation when
-                  // switching from scroll timeline to document timeline.
-                  double progress = previous_timeline->CurrentTime().value() /
-                                    previous_timeline->GetDuration().value();
-
-                  AnimationTimeDelta end_time = std::max(
-                      specified_timing.start_delay.AsTimeValue() +
-                          MultiplyZeroAlwaysGivesZero(
-                              specified_timing.iteration_duration.value_or(
-                                  AnimationTimeDelta()),
-                              specified_timing.iteration_count) +
-                          specified_timing.end_delay.AsTimeValue(),
-                      AnimationTimeDelta());
-
-                  inherited_time = progress * end_time;
-                }
-              }
-            }
-          }
-
+          CSSAnimationProxy animation_proxy(timeline, animation,
+                                            !will_be_playing, range_start,
+                                            range_end, timing);
           update.UpdateAnimation(
               existing_animation_index, animation,
               *MakeGarbageCollected<InertEffect>(
@@ -1554,8 +1736,7 @@
                       resolver, element, animating_element, writing_direction,
                       parent_style, name, keyframe_timing_function.get(),
                       composite, i, timeline),
-                  timing, is_paused, inherited_time, timeline_duration,
-                  animation->playbackRate()),
+                  timing, animation_proxy),
               specified_timing, keyframes_rule, timeline,
               animation_data->PlayStateList(), range_start, range_end);
           if (toggle_pause_state)
@@ -1565,16 +1746,11 @@
         DCHECK(!is_animation_style_change);
         AnimationTimeline* timeline = ComputeTimeline(
             &element, style_timeline, update, /* existing_timeline */ nullptr);
-        absl::optional<AnimationTimeDelta> inherited_time =
-            AnimationTimeDelta();
 
-        absl::optional<AnimationTimeDelta> timeline_duration;
-        if (timeline) {
-          timeline_duration = timeline->GetDuration();
-          if (!timeline->IsMonotonicallyIncreasing()) {
-            inherited_time = timeline->CurrentTime();
-          }
-        }
+        CSSAnimationProxy animation_proxy(timeline, /* animation */ nullptr,
+                                          is_paused, range_start, range_end,
+                                          timing);
+
         update.StartAnimation(
             name, name_index, i,
             *MakeGarbageCollected<InertEffect>(
@@ -1582,7 +1758,7 @@
                                           writing_direction, parent_style, name,
                                           keyframe_timing_function.get(),
                                           composite, i, timeline),
-                timing, is_paused, inherited_time, timeline_duration, 1.0),
+                timing, animation_proxy),
             specified_timing, keyframes_rule, timeline,
             animation_data->PlayStateList(), range_start, range_end);
       }
@@ -1884,17 +2060,7 @@
       css_animation.setTimeline(entry.timeline);
       css_animation.ResetIgnoreCSSTimeline();
     }
-    if (!css_animation.GetIgnoreCSSRangeStart() &&
-        css_animation.GetRangeStartInternal() != entry.range_start) {
-      css_animation.SetRangeStartInternal(entry.range_start);
-      css_animation.ResetIgnoreCSSRangeStart();
-    }
-    if (!css_animation.GetIgnoreCSSRangeEnd() &&
-        css_animation.GetRangeEndInternal() != entry.range_end) {
-      css_animation.SetRangeEndInternal(entry.range_end);
-      css_animation.ResetIgnoreCSSRangeEnd();
-    }
-
+    css_animation.SetRange(entry.range_start, entry.range_end);
     running_animations_[entry.index]->Update(entry);
     entry.animation->Update(kTimingUpdateOnDemand);
   }
@@ -1929,8 +2095,7 @@
     if (inert_animation->Paused())
       animation->pause();
     animation->ResetIgnoreCSSPlayState();
-    animation->SetRangeStartInternal(entry.range_start);
-    animation->SetRangeEndInternal(entry.range_end);
+    animation->SetRange(entry.range_start, entry.range_end);
     animation->ResetIgnoreCSSRangeStart();
     animation->ResetIgnoreCSSRangeEnd();
     animation->Update(kTimingUpdateOnDemand);
@@ -2274,7 +2439,7 @@
       property, state.before_change_style, &state.base_style,
       reversing_adjusted_start_value, reversing_shortening_factor,
       *MakeGarbageCollected<InertEffect>(
-          model, timing, false, AnimationTimeDelta(), absl::nullopt, 1.0));
+          model, timing, CSSTransitionProxy(AnimationTimeDelta())));
   DCHECK(!state.animating_element.GetElementAnimations() ||
          !state.animating_element.GetElementAnimations()
               ->IsAnimationStyleChange());
@@ -2489,8 +2654,8 @@
         continue;
 
       auto* inert_animation_for_sampling = MakeGarbageCollected<InertEffect>(
-          effect->Model(), effect->SpecifiedTiming(), false, current_time,
-          /* timeline_duration */ absl::nullopt, animation->playbackRate());
+          effect->Model(), effect->SpecifiedTiming(),
+          CSSTransitionProxy(current_time));
 
       HeapVector<Member<Interpolation>> sample;
       inert_animation_for_sampling->Sample(sample);
diff --git a/third_party/blink/renderer/core/animation/effect_stack_test.cc b/third_party/blink/renderer/core/animation/effect_stack_test.cc
index 31fecce..0b2a7060 100644
--- a/third_party/blink/renderer/core/animation/effect_stack_test.cc
+++ b/third_party/blink/renderer/core/animation/effect_stack_test.cc
@@ -76,7 +76,7 @@
     Timing timing;
     timing.fill_mode = Timing::FillMode::BOTH;
     return MakeGarbageCollected<InertEffect>(
-        effect, timing, false, AnimationTimeDelta(), absl::nullopt, 1.0);
+        effect, timing, animation_test_helpers::TestAnimationProxy());
   }
 
   KeyframeEffect* MakeKeyframeEffect(KeyframeEffectModelBase* effect,
diff --git a/third_party/blink/renderer/core/animation/inert_effect.cc b/third_party/blink/renderer/core/animation/inert_effect.cc
index ee8bdfc..cb968ab8 100644
--- a/third_party/blink/renderer/core/animation/inert_effect.cc
+++ b/third_party/blink/renderer/core/animation/inert_effect.cc
@@ -36,19 +36,18 @@
 
 InertEffect::InertEffect(KeyframeEffectModelBase* model,
                          const Timing& timing,
-                         bool paused,
-                         absl::optional<AnimationTimeDelta> inherited_time,
-                         absl::optional<AnimationTimeDelta> timeline_duration,
-                         double playback_rate)
+                         const AnimationProxy& proxy)
     : AnimationEffect(timing),
       model_(model),
-      paused_(paused),
-      inherited_time_(inherited_time),
-      timeline_duration_(timeline_duration),
-      playback_rate_(playback_rate) {}
+      paused_(proxy.Paused()),
+      inherited_time_(proxy.InheritedTime()),
+      timeline_duration_(proxy.TimelineDuration()),
+      intrinsic_iteration_duration_(proxy.IntrinsicIterationDuration()),
+      playback_rate_(proxy.PlaybackRate()),
+      at_scroll_timeline_boundary_(proxy.AtScrollTimelineBoundary()) {}
 
 void InertEffect::Sample(HeapVector<Member<Interpolation>>& result) const {
-  UpdateInheritedTime(inherited_time_, /* at_scroll_timeline_boundary */ false,
+  UpdateInheritedTime(inherited_time_, at_scroll_timeline_boundary_,
                       /* is_idle */ false, playback_rate_,
                       kTimingUpdateOnDemand);
   if (!IsInEffect()) {
@@ -78,6 +77,10 @@
   return timeline_duration_;
 }
 
+AnimationTimeDelta InertEffect::IntrinsicIterationDuration() const {
+  return intrinsic_iteration_duration_;
+}
+
 void InertEffect::Trace(Visitor* visitor) const {
   visitor->Trace(model_);
   AnimationEffect::Trace(visitor);
diff --git a/third_party/blink/renderer/core/animation/inert_effect.h b/third_party/blink/renderer/core/animation/inert_effect.h
index 27efe66..d567baf 100644
--- a/third_party/blink/renderer/core/animation/inert_effect.h
+++ b/third_party/blink/renderer/core/animation/inert_effect.h
@@ -37,17 +37,22 @@
 
 namespace blink {
 
+class AnimationProxy {
+ public:
+  virtual bool AtScrollTimelineBoundary() const = 0;
+  virtual absl::optional<AnimationTimeDelta> TimelineDuration() const = 0;
+  virtual AnimationTimeDelta IntrinsicIterationDuration() const = 0;
+  virtual double PlaybackRate() const = 0;
+  virtual bool Paused() const = 0;
+  virtual absl::optional<AnimationTimeDelta> InheritedTime() const = 0;
+};
+
 // Lightweight subset of KeyframeEffect.
 // Used to transport data for deferred KeyframeEffect construction and one off
 // Interpolation sampling.
 class CORE_EXPORT InertEffect final : public AnimationEffect {
  public:
-  InertEffect(KeyframeEffectModelBase*,
-              const Timing&,
-              bool paused,
-              absl::optional<AnimationTimeDelta> inherited_time,
-              absl::optional<AnimationTimeDelta> timeline_duration,
-              double playback_rate);
+  InertEffect(KeyframeEffectModelBase*, const Timing&, const AnimationProxy&);
 
   void Sample(HeapVector<Member<Interpolation>>&) const;
   KeyframeEffectModelBase* Model() const { return model_.Get(); }
@@ -66,6 +71,7 @@
       absl::optional<AnimationTimeDelta> inherited_time,
       AnimationTimeDelta time_to_next_iteration) const override;
   absl::optional<AnimationTimeDelta> TimelineDuration() const override;
+  AnimationTimeDelta IntrinsicIterationDuration() const override;
 
  private:
   Member<KeyframeEffectModelBase> model_;
@@ -73,7 +79,9 @@
   absl::optional<AnimationTimeDelta> inherited_time_;
   absl::optional<TimelinePhase> inherited_phase_;
   absl::optional<AnimationTimeDelta> timeline_duration_;
+  AnimationTimeDelta intrinsic_iteration_duration_;
   double playback_rate_;
+  bool at_scroll_timeline_boundary_;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/animation/inert_effect_test.cc b/third_party/blink/renderer/core/animation/inert_effect_test.cc
index d8c158e..9c5f93c 100644
--- a/third_party/blink/renderer/core/animation/inert_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/inert_effect_test.cc
@@ -24,8 +24,7 @@
     timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(1000);
 
     auto* inert_effect = MakeGarbageCollected<InertEffect>(
-        opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
+        opacity_model, timing, animation_test_helpers::TestAnimationProxy());
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -39,8 +38,7 @@
     timing.start_delay = Timing::Delay(ANIMATION_TIME_DELTA_FROM_SECONDS(500));
 
     auto* inert_effect = MakeGarbageCollected<InertEffect>(
-        opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
+        opacity_model, timing, animation_test_helpers::TestAnimationProxy());
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -53,9 +51,12 @@
     timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(1000);
     timing.start_delay = Timing::Delay(ANIMATION_TIME_DELTA_FROM_SECONDS(500));
 
-    auto* inert_effect = MakeGarbageCollected<InertEffect>(
-        opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-        /* timeline_duration */ absl::nullopt, /* playback_rate */ -1.0);
+    animation_test_helpers::TestAnimationProxy proxy;
+    proxy.SetPlaybackRate(-1);
+
+    auto* inert_effect =
+        MakeGarbageCollected<InertEffect>(opacity_model, timing, proxy);
+
     HeapVector<Member<Interpolation>> interpolations;
     // Calling Sample ensures Timing is calculated.
     inert_effect->Sample(interpolations);
@@ -73,12 +74,10 @@
   Timing timing;
 
   auto* opacity_effect = MakeGarbageCollected<InertEffect>(
-      opacity_model, timing, /* paused */ false, AnimationTimeDelta(),
-      /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
+      opacity_model, timing, animation_test_helpers::TestAnimationProxy());
 
   auto* color_effect = MakeGarbageCollected<InertEffect>(
-      color_model, timing, /* paused */ false, AnimationTimeDelta(),
-      /* timeline_duration */ absl::nullopt, /* playback_rate */ 1.0);
+      color_model, timing, animation_test_helpers::TestAnimationProxy());
 
   EXPECT_TRUE(opacity_effect->Affects(PropertyHandle(GetCSSPropertyOpacity())));
   EXPECT_FALSE(opacity_effect->Affects(PropertyHandle(GetCSSPropertyColor())));
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index 6e106b37..5559a5c 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -40,6 +40,7 @@
 #include "third_party/blink/renderer/core/animation/effect_input.h"
 #include "third_party/blink/renderer/core/animation/element_animations.h"
 #include "third_party/blink/renderer/core/animation/sampled_effect.h"
+#include "third_party/blink/renderer/core/animation/timing_calculations.h"
 #include "third_party/blink/renderer/core/animation/timing_input.h"
 #include "third_party/blink/renderer/core/animation/view_timeline.h"
 #include "third_party/blink/renderer/core/css/parser/css_selector_parser.h"
@@ -664,14 +665,16 @@
 }
 
 void KeyframeEffect::ClearEffects() {
-  if (!sampled_effect_)
+  if (!sampled_effect_) {
     return;
+  }
   sampled_effect_->Clear();
   sampled_effect_ = nullptr;
   if (GetAnimation())
     GetAnimation()->RestartAnimationOnCompositor();
-  if (!effect_target_->GetDocument().Lifecycle().InDetach())
+  if (!effect_target_->GetDocument().Lifecycle().InDetach()) {
     effect_target_->SetNeedsAnimationStyleRecalc();
+  }
   auto* svg_element = DynamicTo<SVGElement>(effect_target_.Get());
   if (RuntimeEnabledFeatures::WebAnimationsSVGEnabled() && svg_element)
     svg_element->ClearWebAnimatedAttributes();
@@ -683,10 +686,11 @@
     return;
   DCHECK(owner_);
   if (IsInEffect() && !owner_->EffectSuppressed() &&
-      !owner_->ReplaceStateRemoved())
+      !owner_->ReplaceStateRemoved()) {
     const_cast<KeyframeEffect*>(this)->ApplyEffects();
-  else
+  } else {
     const_cast<KeyframeEffect*>(this)->ClearEffects();
+  }
 }
 
 void KeyframeEffect::Attach(AnimationEffectOwner* owner) {
@@ -719,16 +723,6 @@
   ClearEffects();
 }
 
-AnimationTimeDelta KeyframeEffect::IntrinsicIterationDuration() const {
-  if (auto* animation = GetAnimation()) {
-    auto* timeline = animation->timeline();
-    if (timeline) {
-      return timeline->CalculateIntrinsicIterationDuration(animation, timing_);
-    }
-  }
-  return AnimationTimeDelta();
-}
-
 AnimationTimeDelta KeyframeEffect::CalculateTimeToEffectChange(
     bool forwards,
     absl::optional<AnimationTimeDelta> local_time,
@@ -766,7 +760,8 @@
       }
       return {};
     case Timing::kPhaseAfter:
-      DCHECK_GE(local_time.value(), after_time);
+      DCHECK(GreaterThanOrEqualToWithinTimeTolerance(local_time.value(),
+                                                     after_time));
       if (forwards) {
         // If an animation has a positive-valued end delay, we need an
         // additional tick at the end time to ensure that the finished event is
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.h b/third_party/blink/renderer/core/animation/keyframe_effect.h
index fbc526a..99411c93 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.h
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.h
@@ -160,7 +160,6 @@
   void DetachTarget(Animation*);
   void RefreshTarget();
   void CountAnimatedProperties() const;
-  AnimationTimeDelta IntrinsicIterationDuration() const override;
   AnimationTimeDelta CalculateTimeToEffectChange(
       bool forwards,
       absl::optional<AnimationTimeDelta> inherited_time,
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.cc b/third_party/blink/renderer/core/animation/scroll_timeline.cc
index 19bd395..b8aab215 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.cc
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.cc
@@ -252,8 +252,7 @@
 
   // Only run calculation for progress based scroll timelines
   if (duration) {
-    // if iteration_duration == "auto" and iterations > 0
-    if (!timing.iteration_duration && timing.iteration_count > 0) {
+    if (timing.iteration_count > 0) {
       // duration represents 100% so we subtract percentage delays and divide it
       // by iteration count to calculate the iteration duration.
       double start_delay = timing.start_delay.relative_delay.value_or(0);
diff --git a/third_party/blink/renderer/core/animation/scroll_timeline.h b/third_party/blink/renderer/core/animation/scroll_timeline.h
index 61ba18a8..407c1f39 100644
--- a/third_party/blink/renderer/core/animation/scroll_timeline.h
+++ b/third_party/blink/renderer/core/animation/scroll_timeline.h
@@ -79,6 +79,16 @@
   AnimationTimeDelta CalculateIntrinsicIterationDuration(
       const Animation*,
       const Timing&) override;
+
+  // TODO(kevers): Support range start and end for scroll-timelines that are not
+  // view timelines.
+  AnimationTimeDelta CalculateIntrinsicIterationDuration(
+      const absl::optional<TimelineOffset>& rangeStart,
+      const absl::optional<TimelineOffset>& rangeEnd,
+      const Timing& timing) override {
+    return CalculateIntrinsicIterationDuration(nullptr, timing);
+  }
+
   AnimationTimeDelta ZeroTime() override { return AnimationTimeDelta(); }
 
   void ServiceAnimations(TimingUpdateReason) override;
diff --git a/third_party/blink/renderer/core/animation/timeline_offset.cc b/third_party/blink/renderer/core/animation/timeline_offset.cc
index df94924..fa30e8b 100644
--- a/third_party/blink/renderer/core/animation/timeline_offset.cc
+++ b/third_party/blink/renderer/core/animation/timeline_offset.cc
@@ -108,11 +108,18 @@
   // TODO(kevers): Keep track of style dependent lengths in order
   // to re-resolve on a style update.
   DCHECK(list.length());
-  const auto& range_name = To<CSSIdentifierValue>(list.Item(0));
-  Length offset = (list.length() == 2u) ? ResolveLength(element, &list.Item(1))
-                                        : Length::Percent(default_percent);
+  NamedRange range_name = NamedRange::kNone;
+  Length offset = Length::Percent(default_percent);
+  if (list.Item(0).IsIdentifierValue()) {
+    range_name = To<CSSIdentifierValue>(list.Item(0)).ConvertTo<NamedRange>();
+    if (list.length() == 2u) {
+      offset = ResolveLength(element, &list.Item(1));
+    }
+  } else {
+    offset = ResolveLength(element, &list.Item(0));
+  }
 
-  return TimelineOffset(range_name.ConvertTo<NamedRange>(), offset);
+  return TimelineOffset(range_name, offset);
 }
 
 /* static */
diff --git a/third_party/blink/renderer/core/animation/timing_calculations.h b/third_party/blink/renderer/core/animation/timing_calculations.h
index 2235c33..d725923 100644
--- a/third_party/blink/renderer/core/animation/timing_calculations.h
+++ b/third_party/blink/renderer/core/animation/timing_calculations.h
@@ -135,7 +135,8 @@
                AnimationTimeDelta());
   if (local_time.value() > active_after_boundary_time ||
       (direction == Timing::AnimationDirection::kForwards &&
-       local_time.value() == active_after_boundary_time &&
+       IsWithinAnimationTimeTolerance(local_time.value(),
+                                      active_after_boundary_time) &&
        !at_progress_timeline_boundary)) {
     return Timing::kPhaseAfter;
   }
diff --git a/third_party/blink/renderer/core/animation/view_timeline.cc b/third_party/blink/renderer/core/animation/view_timeline.cc
index 34d4415..e285b639 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.cc
+++ b/third_party/blink/renderer/core/animation/view_timeline.cc
@@ -261,20 +261,23 @@
 AnimationTimeDelta ViewTimeline::CalculateIntrinsicIterationDuration(
     const Animation* animation,
     const Timing& timing) {
+  return CalculateIntrinsicIterationDuration(animation->GetRangeStartInternal(),
+                                             animation->GetRangeEndInternal(),
+                                             timing);
+}
+
+AnimationTimeDelta ViewTimeline::CalculateIntrinsicIterationDuration(
+    const absl::optional<TimelineOffset>& rangeStart,
+    const absl::optional<TimelineOffset>& rangeEnd,
+    const Timing& timing) {
   absl::optional<AnimationTimeDelta> duration = GetDuration();
 
   // Only run calculation for progress based scroll timelines
   if (duration && timing.iteration_count > 0) {
     double active_interval = 1;
 
-    double start =
-        animation->GetRangeStartInternal()
-            ? ToFractionalOffset(animation->GetRangeStartInternal().value())
-            : 0;
-    double end =
-        animation->GetRangeEndInternal()
-            ? ToFractionalOffset(animation->GetRangeEndInternal().value())
-            : 1;
+    double start = rangeStart ? ToFractionalOffset(rangeStart.value()) : 0;
+    double end = rangeEnd ? ToFractionalOffset(rangeEnd.value()) : 1;
 
     active_interval -= start;
     active_interval -= (1 - end);
@@ -361,8 +364,9 @@
     start_offset_ = start_offset;
     end_offset_ = end_offset;
 
-    for (auto animation : GetAnimations())
+    for (auto animation : GetAnimations()) {
       animation->InvalidateNormalizedTiming();
+    }
   }
 
   return absl::make_optional<ScrollOffsets>(start_offset, end_offset);
diff --git a/third_party/blink/renderer/core/animation/view_timeline.h b/third_party/blink/renderer/core/animation/view_timeline.h
index cf6a2c2..afd3fec 100644
--- a/third_party/blink/renderer/core/animation/view_timeline.h
+++ b/third_party/blink/renderer/core/animation/view_timeline.h
@@ -44,6 +44,11 @@
       const Animation*,
       const Timing&) override;
 
+  AnimationTimeDelta CalculateIntrinsicIterationDuration(
+      const absl::optional<TimelineOffset>& rangeStart,
+      const absl::optional<TimelineOffset>& rangeEnd,
+      const Timing&) override;
+
   // IDL API implementation.
   Element* subject() const;
 
diff --git a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
index 4c3909cc..c0164431 100644
--- a/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/computed_style_utils.cc
@@ -2288,7 +2288,9 @@
     return MakeGarbageCollected<CSSIdentifierValue>(CSSValueID::kNormal);
   }
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
-  list->Append(*MakeGarbageCollected<CSSIdentifierValue>(offset->name));
+  if (offset->name != TimelineOffset::NamedRange::kNone) {
+    list->Append(*MakeGarbageCollected<CSSIdentifierValue>(offset->name));
+  }
   if (offset->offset != default_offset) {
     list->Append(*ComputedStyleUtils::ZoomAdjustedPixelValueForLength(
         offset->offset, style));
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 3a6fa85..ff3e277 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -4354,16 +4354,17 @@
   }
   CSSValueList* list = CSSValueList::CreateSpaceSeparated();
   CSSValue* range_name = ConsumeTimelineRangeName(range);
-  if (!range_name) {
-    return nullptr;
+  if (range_name) {
+    list->Append(*range_name);
   }
-  list->Append(*range_name);
   CSSPrimitiveValue* percentage = ConsumeLengthOrPercent(
       range, context, CSSPrimitiveValue::ValueRange::kAll);
   if (percentage &&
-      !(percentage->IsPercentage() &&
+      !(range_name && percentage->IsPercentage() &&
         percentage->GetValue<double>() == default_offset_percent)) {
     list->Append(*percentage);
+  } else if (!range_name) {
+    return nullptr;
   }
   return list;
 }
diff --git a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
index e24b363b..e2133ea 100644
--- a/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/shorthands/shorthands_custom.cc
@@ -300,8 +300,10 @@
   if (start_range && start_range->IsValueList() && !end_range) {
     CSSValueList* implied_end = CSSValueList::CreateSpaceSeparated();
     const CSSValue& name = To<CSSValueList>(start_range)->First();
-    implied_end->Append(name);
-    end_range = implied_end;
+    if (name.IsIdentifierValue()) {
+      implied_end->Append(name);
+      end_range = implied_end;
+    }
   }
 
   if (!start_range) {
diff --git a/third_party/blink/renderer/core/css/resolver/css_to_style_map.cc b/third_party/blink/renderer/core/css/resolver/css_to_style_map.cc
index aba8cc46..b1c8289 100644
--- a/third_party/blink/renderer/core/css/resolver/css_to_style_map.cc
+++ b/third_party/blink/renderer/core/css/resolver/css_to_style_map.cc
@@ -484,12 +484,19 @@
   const auto& list = To<CSSValueList>(value);
   DCHECK_GE(list.length(), 1u);
   DCHECK_LE(list.length(), 2u);
-  const auto& range_name = To<CSSIdentifierValue>(list.Item(0));
-  Length percent = (list.length() == 2u) ? StyleBuilderConverter::ConvertLength(
-                                               state, list.Item(1))
-                                         : Length::Percent(default_percent);
-  return TimelineOffset(range_name.ConvertTo<TimelineOffset::NamedRange>(),
-                        percent);
+  TimelineOffset::NamedRange range_name = TimelineOffset::NamedRange::kNone;
+  Length offset = Length::Percent(default_percent);
+  if (list.Item(0).IsIdentifierValue()) {
+    range_name = To<CSSIdentifierValue>(list.Item(0))
+                     .ConvertTo<TimelineOffset::NamedRange>();
+    if (list.length() == 2u) {
+      offset = StyleBuilderConverter::ConvertLength(state, list.Item(1));
+    }
+  } else {
+    offset = StyleBuilderConverter::ConvertLength(state, list.Item(0));
+  }
+
+  return TimelineOffset(range_name, offset);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index b390809..9ac49ac 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -3133,7 +3133,7 @@
       DCHECK(FlatTreeTraversal::ContainsIncludingPseudoElement(
           container, *layout_tree_rebuild_root_.GetRootNode()));
     }
-    RebuildLayoutTree(RebuildTransitionPseudoTree::kNo);
+    RebuildLayoutTree(&container);
   }
 
   if (container == GetDocument().documentElement()) {
@@ -3210,17 +3210,27 @@
   }
 }
 
-void StyleEngine::RebuildLayoutTreeForTraversalRootAncestors(Element* parent) {
+void StyleEngine::RebuildLayoutTreeForTraversalRootAncestors(
+    Element* parent,
+    Element* container_parent) {
+  bool is_container_ancestor = false;
+
   for (auto* ancestor = parent; ancestor;
        ancestor = ancestor->GetReattachParent()) {
-    ancestor->RebuildLayoutTreeForTraversalRootAncestor();
+    if (ancestor == container_parent) {
+      is_container_ancestor = true;
+    }
+    if (is_container_ancestor) {
+      ancestor->RebuildLayoutTreeForSizeContainerAncestor();
+    } else {
+      ancestor->RebuildLayoutTreeForTraversalRootAncestor();
+    }
     ancestor->ClearChildNeedsStyleRecalc();
     ancestor->ClearChildNeedsReattachLayoutTree();
   }
 }
 
-void StyleEngine::RebuildLayoutTree(
-    RebuildTransitionPseudoTree rebuild_transition_pseudo_tree) {
+void StyleEngine::RebuildLayoutTree(Element* size_container) {
   bool propagate_to_root = false;
   {
     DCHECK(GetDocument().documentElement());
@@ -3237,9 +3247,11 @@
       root_element.RebuildLayoutTree(whitespace_attacher);
     }
 
-    RebuildLayoutTreeForTraversalRootAncestors(
-        root_element.GetReattachParent());
-    if (rebuild_transition_pseudo_tree == RebuildTransitionPseudoTree::kYes) {
+    Element* container_parent =
+        size_container ? size_container->GetReattachParent() : nullptr;
+    RebuildLayoutTreeForTraversalRootAncestors(root_element.GetReattachParent(),
+                                               container_parent);
+    if (size_container == nullptr) {
       document_->documentElement()->RebuildTransitionPseudoLayoutTree(
           view_transition_names_);
     }
@@ -3250,7 +3262,7 @@
   if (propagate_to_root) {
     PropagateWritingModeAndDirectionToHTMLRoot();
     if (NeedsLayoutTreeRebuild()) {
-      RebuildLayoutTree(rebuild_transition_pseudo_tree);
+      RebuildLayoutTree(size_container);
     }
   }
 }
@@ -3269,7 +3281,8 @@
 
   base::AutoReset<bool> rebuild_scope(&in_layout_tree_rebuild_, true);
   container.ReattachLayoutTreeChildren(base::PassKey<StyleEngine>());
-  RebuildLayoutTreeForTraversalRootAncestors(&container);
+  RebuildLayoutTreeForTraversalRootAncestors(&container,
+                                             container.GetReattachParent());
   layout_tree_rebuild_root_.Clear();
 }
 
@@ -3299,7 +3312,7 @@
     if (NeedsLayoutTreeRebuild()) {
       TRACE_EVENT0("blink,blink_style", "Document::rebuildLayoutTree");
       SCOPED_BLINK_UMA_HISTOGRAM_TIMER_HIGHRES("Style.RebuildLayoutTreeTime");
-      RebuildLayoutTree(RebuildTransitionPseudoTree::kYes);
+      RebuildLayoutTree();
     }
   } else {
     style_recalc_root_.Clear();
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 3730954..3b70f354 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -585,9 +585,7 @@
   void RecalcStyle();
 
   void ClearEnsuredDescendantStyles(Element& element);
-  enum class RebuildTransitionPseudoTree { kYes, kNo };
-  void RebuildLayoutTree(
-      RebuildTransitionPseudoTree rebuild_transition_pseudo_tree);
+  void RebuildLayoutTree(Element* size_container = nullptr);
   bool InRebuildLayoutTree() const { return in_layout_tree_rebuild_; }
   bool InDOMRemoval() const { return in_dom_removal_; }
   bool InContainerQueryStyleRecalc() const {
@@ -799,7 +797,8 @@
   // removal which caused a layout subtree to be detached.
   void MarkForLayoutTreeChangesAfterDetach();
 
-  void RebuildLayoutTreeForTraversalRootAncestors(Element* parent);
+  void RebuildLayoutTreeForTraversalRootAncestors(Element* parent,
+                                                  Element* container_parent);
 
   // Separate path for layout tree rebuild for re-attaching children of a
   // fieldset size query container, or a size query container which must use
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.cc b/third_party/blink/renderer/core/css/style_property_serializer.cc
index 1e8d43e..8786609b 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.cc
+++ b/third_party/blink/renderer/core/css/style_property_serializer.cc
@@ -959,19 +959,21 @@
   }
   DCHECK_GE(list->length(), 1u);
   DCHECK_LE(list->length(), 2u);
-  const auto& name = To<CSSIdentifierValue>(list->Item(0));
-  double offset_percent = -1.0;
+  CSSValueID name = CSSValueID::kNormal;
+  double offset_percent = default_offset_percent;
 
-  if (list->length() == 1u) {
-    // <ident>
-    offset_percent = default_offset_percent;
+  if (list->Item(0).IsIdentifierValue()) {
+    name = To<CSSIdentifierValue>(list->Item(0)).GetValueID();
+    if (list->length() == 2u) {
+      const auto& offset = To<CSSPrimitiveValue>(list->Item(1));
+      offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0;
+    }
   } else {
-    // <ident> <length-percentage>
-    const auto& offset = To<CSSPrimitiveValue>(list->Item(1));
+    const auto& offset = To<CSSPrimitiveValue>(list->Item(0));
     offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0;
   }
 
-  return {name.GetValueID(), offset_percent};
+  return {name, offset_percent};
 }
 
 CSSValue* AnimationRangeShorthandValueItem(wtf_size_t index,
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 209e220..a8ae825b 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -612,6 +612,9 @@
     RebuildMarkerLayoutTree(whitespace_attacher);
     HandleSubtreeModifications();
   }
+  void RebuildLayoutTreeForSizeContainerAncestor() {
+    RebuildFirstLetterLayoutTree();
+  }
   bool NeedsRebuildChildLayoutTrees(
       const WhitespaceAttacher& whitespace_attacher) const {
     return ChildNeedsReattachLayoutTree() || NeedsWhitespaceChildrenUpdate() ||
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 38beb7c..a4df7dca 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -681,7 +681,11 @@
     }
   }
 
-  To<LayoutBox>(root).LayoutSubtreeRoot();
+  if (RuntimeEnabledFeatures::LayoutNewSubtreeRootEnabled()) {
+    To<LayoutBox>(root).LayoutSubtreeRoot();
+  } else {
+    To<LayoutBox>(root).LayoutSubtreeRootOld();
+  }
   return true;
 }
 
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.cc b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
index 553d5b8..849e563 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.cc
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.cc
@@ -233,7 +233,7 @@
     return visible_scroll_snapport;
 
   const ComputedStyle* style = LayoutViewport().GetLayoutBox()->Style();
-  LayoutRectOutsets padding(
+  visible_scroll_snapport.ContractEdges(
       MinimumValueForLength(style->ScrollPaddingTop(),
                             visible_scroll_snapport.Height()),
       MinimumValueForLength(style->ScrollPaddingRight(),
@@ -242,7 +242,6 @@
                             visible_scroll_snapport.Height()),
       MinimumValueForLength(style->ScrollPaddingLeft(),
                             visible_scroll_snapport.Width()));
-  visible_scroll_snapport.Contract(padding);
 
   return visible_scroll_snapport;
 }
diff --git a/third_party/blink/renderer/core/html/portal/portal_contents.cc b/third_party/blink/renderer/core/html/portal/portal_contents.cc
index 7c7c424..a45b0df0 100644
--- a/third_party/blink/renderer/core/html/portal/portal_contents.cc
+++ b/third_party/blink/renderer/core/html/portal/portal_contents.cc
@@ -38,10 +38,17 @@
     : document_(portal_element.GetDocument()),
       portal_element_(&portal_element),
       portal_token_(portal_token),
-      remote_portal_(std::move(remote_portal)),
-      portal_client_receiver_(this, std::move(portal_client_receiver)) {
+      remote_portal_(portal_element.GetDocument().GetExecutionContext()),
+      portal_client_receiver_(
+          this,
+          portal_element.GetDocument().GetExecutionContext()) {
+  remote_portal_.Bind(std::move(remote_portal),
+                      document_->GetTaskRunner(TaskType::kInternalDefault));
   remote_portal_.set_disconnect_handler(WTF::BindOnce(
       &PortalContents::DisconnectHandler, WrapWeakPersistent(this)));
+  portal_client_receiver_.Bind(
+      std::move(portal_client_receiver),
+      document_->GetTaskRunner(TaskType::kInternalDefault));
   DocumentPortals::GetOrCreate(GetDocument()).RegisterPortalContents(this);
 }
 
@@ -215,6 +222,8 @@
   visitor->Trace(document_);
   visitor->Trace(portal_element_);
   visitor->Trace(activation_delegate_);
+  visitor->Trace(remote_portal_);
+  visitor->Trace(portal_client_receiver_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/portal/portal_contents.h b/third_party/blink/renderer/core/html/portal/portal_contents.h
index 8f5b6ff..294ba57 100644
--- a/third_party/blink/renderer/core/html/portal/portal_contents.h
+++ b/third_party/blink/renderer/core/html/portal/portal_contents.h
@@ -6,8 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_HTML_PORTAL_PORTAL_CONTENTS_H_
 
 #include "base/memory/scoped_refptr.h"
-#include "mojo/public/cpp/bindings/associated_receiver.h"
-#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "services/network/public/mojom/referrer_policy.mojom-shared.h"
@@ -15,7 +13,8 @@
 #include "third_party/blink/public/common/tokens/tokens.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom-blink.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
-#include "third_party/blink/renderer/platform/wtf/gc_plugin.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_receiver.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_remote.h"
 
 namespace blink {
 
@@ -116,10 +115,9 @@
   absl::optional<PortalToken> portal_token_;
 
   // Both of these will be reset once Destroy has been called.
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedRemote<mojom::blink::Portal> remote_portal_;
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedReceiver<mojom::blink::PortalClient> portal_client_receiver_;
+  HeapMojoAssociatedRemote<mojom::blink::Portal> remote_portal_;
+  HeapMojoAssociatedReceiver<mojom::blink::PortalClient, PortalContents>
+      portal_client_receiver_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
index 8707bcd93..6ae7fb3 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_geometry.cc
@@ -63,10 +63,10 @@
   // TODO(szager): Make sure the spec is clear that left/right margins are
   // resolved against width and not height.
   const PhysicalRect& rect = resolution_rect.value_or(expand_rect);
-  LayoutRectOutsets outsets(ComputeMargin(margin[0], rect.Height(), zoom),
-                            ComputeMargin(margin[1], rect.Width(), zoom),
-                            ComputeMargin(margin[2], rect.Height(), zoom),
-                            ComputeMargin(margin[3], rect.Width(), zoom));
+  NGPhysicalBoxStrut outsets(ComputeMargin(margin[0], rect.Height(), zoom),
+                             ComputeMargin(margin[1], rect.Width(), zoom),
+                             ComputeMargin(margin[2], rect.Height(), zoom),
+                             ComputeMargin(margin[3], rect.Width(), zoom));
   expand_rect.Expand(outsets);
 }
 
diff --git a/third_party/blink/renderer/core/layout/build.gni b/third_party/blink/renderer/core/layout/build.gni
index df9a46c1..6a80341 100644
--- a/third_party/blink/renderer/core/layout/build.gni
+++ b/third_party/blink/renderer/core/layout/build.gni
@@ -165,8 +165,6 @@
   "layout_view_transition_content.h",
   "layout_word_break.cc",
   "layout_word_break.h",
-  "line/line_orientation_utils.cc",
-  "line/line_orientation_utils.h",
   "list_marker.cc",
   "list_marker.h",
   "map_coordinates_flags.h",
@@ -720,7 +718,6 @@
   "layout_theme_test.cc",
   "layout_video_test.cc",
   "layout_view_test.cc",
-  "line/line_orientation_utils_test.cc",
   "list_marker_test.cc",
   "map_coordinates_test.cc",
   "min_max_size_test.cc",
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 4e964f40..3a31e0e6 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -1002,6 +1002,33 @@
 
 void LayoutBox::LayoutSubtreeRoot() {
   NOT_DESTROYED();
+  const auto* previous_result = GetSingleCachedLayoutResult();
+  DCHECK(previous_result);
+  auto space = previous_result->GetConstraintSpaceForCaching();
+  DCHECK_EQ(space.GetWritingMode(), StyleRef().GetWritingMode());
+  const NGLayoutResult* result = NGBlockNode(this).Layout(space);
+  GetDocument().GetFrame()->GetInputMethodController().DidLayoutSubtree(*this);
+
+  // Even if we are a layout root, our baseline may have shifted. In this
+  // (rare) case, mark our containing-block for layout.
+  //
+  // NOTE: We could weaken the constraints in ObjectIsRelayoutBoundary, and use
+  // this technique to detect size-changes, etc if we wanted to expand this
+  // optimization.
+  const auto& previous_fragment =
+      To<NGPhysicalBoxFragment>(previous_result->PhysicalFragment());
+  const auto& fragment = To<NGPhysicalBoxFragment>(result->PhysicalFragment());
+  if (previous_fragment.FirstBaseline() != fragment.FirstBaseline() ||
+      previous_fragment.LastBaseline() != fragment.LastBaseline()) {
+    if (auto* containing_block = ContainingBlock()) {
+      containing_block->SetNeedsLayout(
+          layout_invalidation_reason::kChildChanged, kMarkContainerChain);
+    }
+  }
+}
+
+void LayoutBox::LayoutSubtreeRootOld() {
+  NOT_DESTROYED();
   if (!IsLayoutNGObject() && GetSingleCachedLayoutResult()) {
     // If this object is laid out by the legacy engine, while its containing
     // block is laid out by NG, it means that we normally (when laying out
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index bdb928d..8dad9d6e 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -854,6 +854,7 @@
   }
 
   void LayoutSubtreeRoot();
+  void LayoutSubtreeRootOld();
 
   void UpdateLayout() override;
   void Paint(const PaintInfo&) const override;
diff --git a/third_party/blink/renderer/core/layout/line/line_orientation_utils.cc b/third_party/blink/renderer/core/layout/line/line_orientation_utils.cc
deleted file mode 100644
index 1a08eca..0000000
--- a/third_party/blink/renderer/core/layout/line/line_orientation_utils.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2018 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/layout/line/line_orientation_utils.h"
-
-namespace blink {
-
-LayoutRectOutsets LineOrientationLayoutRectOutsets(
-    const LayoutRectOutsets& outsets,
-    WritingMode writing_mode) {
-  if (!IsHorizontalWritingMode(writing_mode)) {
-    return LayoutRectOutsets(outsets.Left(), outsets.Bottom(), outsets.Right(),
-                             outsets.Top());
-  }
-  return outsets;
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/line/line_orientation_utils.h b/third_party/blink/renderer/core/layout/line/line_orientation_utils.h
deleted file mode 100644
index d6c4deb..0000000
--- a/third_party/blink/renderer/core/layout/line/line_orientation_utils.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2018 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_LAYOUT_LINE_LINE_ORIENTATION_UTILS_H_
-#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_LINE_ORIENTATION_UTILS_H_
-
-#include "third_party/blink/renderer/core/core_export.h"
-#include "third_party/blink/renderer/platform/geometry/layout_rect_outsets.h"
-#include "third_party/blink/renderer/platform/text/writing_mode.h"
-
-namespace blink {
-
-// Produces a new LayoutRectOutsets in line orientation
-// (https://www.w3.org/TR/css-writing-modes-3/#line-orientation), whose
-// - |top| is the logical 'over',
-// - |right| is the logical 'line right',
-// - |bottom| is the logical 'under',
-// - |left| is the logical 'line left'.
-CORE_EXPORT LayoutRectOutsets
-LineOrientationLayoutRectOutsets(const LayoutRectOutsets&, WritingMode);
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LINE_LINE_ORIENTATION_UTILS_H_
diff --git a/third_party/blink/renderer/core/layout/line/line_orientation_utils_test.cc b/third_party/blink/renderer/core/layout/line/line_orientation_utils_test.cc
deleted file mode 100644
index 4a43925..0000000
--- a/third_party/blink/renderer/core/layout/line/line_orientation_utils_test.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2015 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/layout/line/line_orientation_utils.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace blink {
-namespace {
-
-TEST(LineOrientationUtilsTest, LineOrientationLayoutRectOutsets_Horizontal) {
-  LayoutRectOutsets outsets(1, 2, 3, 4);
-  EXPECT_EQ(
-      LayoutRectOutsets(1, 2, 3, 4),
-      LineOrientationLayoutRectOutsets(outsets, WritingMode::kHorizontalTb));
-}
-
-TEST(LineOrientationUtilsTest, LineOrientationLayoutRectOutsets_Vertical) {
-  LayoutRectOutsets outsets(1, 2, 3, 4);
-  EXPECT_EQ(
-      LayoutRectOutsets(4, 3, 2, 1),
-      LineOrientationLayoutRectOutsets(outsets, WritingMode::kVerticalLr));
-  EXPECT_EQ(
-      LayoutRectOutsets(4, 3, 2, 1),
-      LineOrientationLayoutRectOutsets(outsets, WritingMode::kVerticalRl));
-}
-
-}  // namespace
-}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc b/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
index 03c95f1..fcf05e2 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_ink_overflow.cc
@@ -7,7 +7,6 @@
 #include "build/chromeos_buildflags.h"
 #include "third_party/blink/renderer/core/layout/geometry/logical_rect.h"
 #include "third_party/blink/renderer/core/layout/geometry/writing_mode_converter.h"
-#include "third_party/blink/renderer/core/layout/line/line_orientation_utils.h"
 #include "third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.h"
 #include "third_party/blink/renderer/core/paint/text_decoration_info.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
@@ -472,13 +471,15 @@
 
   const WritingMode writing_mode = style.GetWritingMode();
   if (ShadowList* text_shadow = style.TextShadow()) {
-    LayoutRectOutsets text_shadow_logical_outsets =
-        LineOrientationLayoutRectOutsets(
-            EnclosingLayoutRectOutsets(
-                text_shadow->RectOutsetsIncludingOriginal()),
-            writing_mode);
-    text_shadow_logical_outsets.ClampNegativeToZero();
-    ink_overflow.Expand(text_shadow_logical_outsets);
+    NGLineBoxStrut text_shadow_logical_outsets =
+        NGPhysicalBoxStrut::Enclosing(
+            text_shadow->RectOutsetsIncludingOriginal())
+            .ConvertToLineLogical({writing_mode, TextDirection::kLtr});
+    ink_overflow.ExpandEdges(
+        text_shadow_logical_outsets.line_over.ClampNegativeToZero(),
+        text_shadow_logical_outsets.inline_end.ClampNegativeToZero(),
+        text_shadow_logical_outsets.line_under.ClampNegativeToZero(),
+        text_shadow_logical_outsets.inline_start.ClampNegativeToZero());
   }
 
   PhysicalRect local_ink_overflow =
diff --git a/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc b/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc
index e70ad0c..8f1cfd32 100644
--- a/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc
+++ b/third_party/blink/renderer/core/page/scrolling/snap_coordinator.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/layout/layout_block.h"
 #include "third_party/blink/renderer/core/layout/layout_box.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
+#include "third_party/blink/renderer/core/layout/ng/geometry/ng_box_strut.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/platform/geometry/length_functions.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
@@ -278,20 +279,19 @@
     PhysicalRect container_rect(snap_container.PhysicalPaddingBoxRect());
 
     const ComputedStyle* container_style = snap_container.Style();
-    LayoutRectOutsets container_padding(
-        // The percentage of scroll-padding is different from that of normal
-        // padding, as scroll-padding resolves the percentage against
-        // corresponding dimension of the scrollport[1], while the normal
-        // padding resolves that against "width".[2,3] We use
-        // MinimumValueForLength here to ensure kAuto is resolved to
-        // LayoutUnit() which is the correct behavior for padding.
-        // [1] https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding
-        //     "relative to the corresponding dimension of the scroll
-        //     container’s
-        //      scrollport"
-        // [2] https://drafts.csswg.org/css-box/#padding-props
-        // [3] See for example LayoutBoxModelObject::ComputedCSSPadding where it
-        //     uses |MinimumValueForLength| but against the "width".
+    // The percentage of scroll-padding is different from that of normal
+    // padding, as scroll-padding resolves the percentage against corresponding
+    // dimension of the scrollport[1], while the normal padding resolves that
+    // against "width".[2,3] We use MinimumValueForLength here to ensure kAuto
+    // is resolved to LayoutUnit() which is the correct behavior for padding.
+    //
+    // [1] https://drafts.csswg.org/css-scroll-snap-1/#scroll-padding
+    //     "relative to the corresponding dimension of the scroll container’s
+    //      scrollport"
+    // [2] https://drafts.csswg.org/css-box/#padding-props
+    // [3] See for example LayoutBoxModelObject::ComputedCSSPadding where it
+    //     uses |MinimumValueForLength| but against the "width".
+    container_rect.ContractEdges(
         MinimumValueForLength(container_style->ScrollPaddingTop(),
                               container_rect.Height()),
         MinimumValueForLength(container_style->ScrollPaddingRight(),
@@ -300,7 +300,6 @@
                               container_rect.Height()),
         MinimumValueForLength(container_style->ScrollPaddingLeft(),
                               container_rect.Width()));
-    container_rect.Contract(container_padding);
     snap_container_data.set_rect(gfx::RectF(container_rect));
 
     if (snap_container_data.scroll_snap_type().strictness ==
@@ -409,7 +408,7 @@
       area_rect, &snap_container,
       kTraverseDocumentBoundaries | kIgnoreScrollOffset);
 
-  LayoutRectOutsets area_margin(
+  NGPhysicalBoxStrut area_margin(
       area_style->ScrollMarginTop(), area_style->ScrollMarginRight(),
       area_style->ScrollMarginBottom(), area_style->ScrollMarginLeft());
   area_rect.Expand(area_margin);
diff --git a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
index fee4e3e..4fb44ee 100644
--- a/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
+++ b/third_party/blink/renderer/core/speculation_rules/document_speculation_rules.cc
@@ -507,8 +507,8 @@
             rule->requires_anonymous_client_ip_when_cross_origin(),
             rule->target_browsing_context_name_hint().value_or(
                 mojom::blink::SpeculationTargetHint::kNoHint),
-            eagerness, rule->no_vary_search_expected().Clone(), rule_set,
-            /*anchor=*/nullptr));
+            eagerness, rule->no_vary_search_expected().Clone(),
+            rule->injection_world(), rule_set, /*anchor=*/nullptr));
       }
     }
   };
@@ -653,7 +653,7 @@
                     rule->target_browsing_context_name_hint().value_or(
                         mojom::blink::SpeculationTargetHint::kNoHint),
                     eagerness, rule->no_vary_search_expected().Clone(),
-                    rule_set, link);
+                    rule->injection_world(), rule_set, link);
             link_candidates->push_back(std::move(candidate));
           }
         };
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_candidate.cc b/third_party/blink/renderer/core/speculation_rules/speculation_candidate.cc
index b0fe440..d5b6c66 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_candidate.cc
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_candidate.cc
@@ -18,6 +18,7 @@
     mojom::blink::SpeculationTargetHint target_hint,
     mojom::blink::SpeculationEagerness eagerness,
     network::mojom::blink::NoVarySearchPtr no_vary_search,
+    mojom::blink::SpeculationInjectionWorld injection_world,
     SpeculationRuleSet* rule_set,
     HTMLAnchorElement* anchor)
     : url_(url),
@@ -28,6 +29,7 @@
       target_hint_(target_hint),
       eagerness_(eagerness),
       no_vary_search_(std::move(no_vary_search)),
+      injection_world_(injection_world),
       rule_set_(rule_set),
       anchor_(anchor) {
   DCHECK(rule_set);
@@ -45,7 +47,7 @@
       mojom::blink::Referrer::New(KURL(referrer_.referrer),
                                   referrer_.referrer_policy),
       requires_anonymous_client_ip_when_cross_origin_, target_hint_, eagerness_,
-      no_vary_search_.Clone());
+      no_vary_search_.Clone(), injection_world_);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_candidate.h b/third_party/blink/renderer/core/speculation_rules/speculation_candidate.h
index 6325241..8bf0a8c 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_candidate.h
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_candidate.h
@@ -30,6 +30,7 @@
                        mojom::blink::SpeculationTargetHint target_hint,
                        mojom::blink::SpeculationEagerness eagerness,
                        network::mojom::blink::NoVarySearchPtr no_vary_search,
+                       mojom::blink::SpeculationInjectionWorld injection_world,
                        SpeculationRuleSet* rule_set,
                        HTMLAnchorElement* anchor);
   virtual ~SpeculationCandidate() = default;
@@ -57,6 +58,7 @@
   const mojom::blink::SpeculationTargetHint target_hint_;
   const mojom::blink::SpeculationEagerness eagerness_;
   const network::mojom::blink::NoVarySearchPtr no_vary_search_;
+  const mojom::blink::SpeculationInjectionWorld injection_world_;
   const Member<SpeculationRuleSet> rule_set_;
   const Member<HTMLAnchorElement> anchor_;
 };
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rule.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rule.cc
index fdf674e5..fb49ad9 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_rule.cc
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rule.cc
@@ -17,14 +17,16 @@
     absl::optional<mojom::blink::SpeculationTargetHint> target_hint,
     absl::optional<network::mojom::ReferrerPolicy> referrer_policy,
     absl::optional<mojom::blink::SpeculationEagerness> eagerness,
-    network::mojom::blink::NoVarySearchPtr no_vary_search_expected)
+    network::mojom::blink::NoVarySearchPtr no_vary_search_expected,
+    mojom::blink::SpeculationInjectionWorld injection_world)
     : urls_(std::move(urls)),
       predicate_(predicate),
       requires_anonymous_client_ip_(requires_anonymous_client_ip),
       target_browsing_context_name_hint_(target_hint),
       referrer_policy_(referrer_policy),
       eagerness_(eagerness),
-      no_vary_search_expected_(std::move(no_vary_search_expected)) {}
+      no_vary_search_expected_(std::move(no_vary_search_expected)),
+      injection_world_(injection_world) {}
 
 SpeculationRule::~SpeculationRule() = default;
 
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rule.h b/third_party/blink/renderer/core/speculation_rules/speculation_rule.h
index 6da39da9..0baaaef5 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_rule.h
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rule.h
@@ -36,7 +36,8 @@
       absl::optional<mojom::blink::SpeculationTargetHint> target_hint,
       absl::optional<network::mojom::ReferrerPolicy>,
       absl::optional<mojom::blink::SpeculationEagerness>,
-      network::mojom::blink::NoVarySearchPtr);
+      network::mojom::blink::NoVarySearchPtr,
+      mojom::blink::SpeculationInjectionWorld);
   ~SpeculationRule();
 
   const Vector<KURL>& urls() const { return urls_; }
@@ -58,6 +59,9 @@
       const {
     return no_vary_search_expected_;
   }
+  mojom::blink::SpeculationInjectionWorld injection_world() const {
+    return injection_world_;
+  }
 
   void Trace(Visitor*) const;
 
@@ -70,6 +74,8 @@
   const absl::optional<network::mojom::ReferrerPolicy> referrer_policy_;
   absl::optional<mojom::blink::SpeculationEagerness> eagerness_;
   network::mojom::blink::NoVarySearchPtr no_vary_search_expected_;
+  mojom::blink::SpeculationInjectionWorld injection_world_ =
+      mojom::blink::SpeculationInjectionWorld::kNone;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc b/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
index c9670b0..5d0a4f31 100644
--- a/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
+++ b/third_party/blink/renderer/core/speculation_rules/speculation_rule_set.cc
@@ -350,9 +350,17 @@
     no_vary_search = std::move(no_vary_search_expected->get_no_vary_search());
   }
 
+  const mojom::blink::SpeculationInjectionWorld world =
+      context->GetCurrentWorld()
+          ? context->GetCurrentWorld()->IsMainWorld()
+                ? mojom::blink::SpeculationInjectionWorld::kMain
+                : mojom::blink::SpeculationInjectionWorld::kIsolated
+          : mojom::blink::SpeculationInjectionWorld::kNone;
+
   return MakeGarbageCollected<SpeculationRule>(
       std::move(urls), document_rule_predicate, requires_anonymous_client_ip,
-      target_hint, referrer_policy, eagerness, std::move(no_vary_search));
+      target_hint, referrer_policy, eagerness, std::move(no_vary_search),
+      world);
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.cc b/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.cc
index 09ccb79..ed26203 100644
--- a/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.cc
+++ b/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.cc
@@ -157,6 +157,9 @@
 void BroadcastChannel::Trace(Visitor* visitor) const {
   ExecutionContextLifecycleObserver::Trace(visitor);
   EventTargetWithInlineData::Trace(visitor);
+  visitor->Trace(receiver_);
+  visitor->Trace(remote_client_);
+  visitor->Trace(associated_remote_);
 }
 
 void BroadcastChannel::OnMessage(BlinkCloneableMessage message) {
@@ -215,10 +218,13 @@
     : ActiveScriptWrappable<BroadcastChannel>({}),
       ExecutionContextLifecycleObserver(execution_context),
       name_(name),
+      receiver_(this, execution_context),
+      remote_client_(execution_context),
       feature_handle_for_scheduler_(
           execution_context->GetScheduler()->RegisterFeature(
               SchedulingPolicy::Feature::kBroadcastChannel,
-              {SchedulingPolicy::DisableBackForwardCache()})) {
+              {SchedulingPolicy::DisableBackForwardCache()})),
+      associated_remote_(execution_context) {
   // Note: We cannot associate per-frame task runner here, but postTask
   //       to it manually via EnqueueEvent, since the current expectation
   //       is to receive messages even after close for which queued before
@@ -242,19 +248,24 @@
   //    shared remote for all BroadcastChannel objects created on that thread to
   //    ensure in-order delivery of messages to the appropriate *WorkerHost
   //    object.
+  auto receiver_task_runner =
+      execution_context->GetTaskRunner(TaskType::kInternalDefault);
+  auto client_task_runner =
+      execution_context->GetTaskRunner(TaskType::kInternalDefault);
   if (receiver.is_valid() && remote.is_valid()) {
-    receiver_.Bind(std::move(receiver));
-    remote_client_.Bind(std::move(remote));
+    receiver_.Bind(std::move(receiver), receiver_task_runner);
+    remote_client_.Bind(std::move(remote), client_task_runner);
   } else if (auto* window = DynamicTo<LocalDOMWindow>(execution_context)) {
     LocalFrame* frame = window->GetFrame();
     if (!frame)
       return;
 
     frame->GetRemoteNavigationAssociatedInterfaces()->GetInterface(
-        associated_remote_.BindNewEndpointAndPassReceiver());
+        associated_remote_.BindNewEndpointAndPassReceiver(
+            execution_context->GetTaskRunner(TaskType::kInternalDefault)));
     associated_remote_->ConnectToChannel(
-        name_, receiver_.BindNewEndpointAndPassRemote(),
-        remote_client_.BindNewEndpointAndPassReceiver());
+        name_, receiver_.BindNewEndpointAndPassRemote(receiver_task_runner),
+        remote_client_.BindNewEndpointAndPassReceiver(client_task_runner));
   } else if (auto* worker_global_scope =
                  DynamicTo<WorkerGlobalScope>(execution_context)) {
     if (worker_global_scope->IsClosing())
@@ -262,8 +273,9 @@
 
     mojo::Remote<mojom::blink::BroadcastChannelProvider>& provider =
         GetWorkerThreadSpecificProvider(*worker_global_scope);
-    provider->ConnectToChannel(name_, receiver_.BindNewEndpointAndPassRemote(),
-                               remote_client_.BindNewEndpointAndPassReceiver());
+    provider->ConnectToChannel(
+        name_, receiver_.BindNewEndpointAndPassRemote(receiver_task_runner),
+        remote_client_.BindNewEndpointAndPassReceiver(client_task_runner));
   } else {
     NOTREACHED();
   }
diff --git a/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h b/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h
index 17df1b3..76ef61af 100644
--- a/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h
+++ b/third_party/blink/renderer/modules/broadcastchannel/broadcast_channel.h
@@ -5,16 +5,15 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_BROADCASTCHANNEL_BROADCAST_CHANNEL_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_BROADCASTCHANNEL_BROADCAST_CHANNEL_H_
 
-#include "mojo/public/cpp/bindings/associated_receiver.h"
-#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/blink/public/mojom/broadcastchannel/broadcast_channel.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/prefinalizer.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_receiver.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_remote.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
-#include "third_party/blink/renderer/platform/wtf/gc_plugin.h"
 
 namespace blink {
 
@@ -102,11 +101,10 @@
   // BroadcastChannelClient receiver for messages sent from the browser to
   // this channel and BroadcastChannelClient remote for messages sent from
   // this channel to the browser.
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedReceiver<mojom::blink::BroadcastChannelClient> receiver_{
-      this};
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedRemote<mojom::blink::BroadcastChannelClient> remote_client_;
+  HeapMojoAssociatedReceiver<mojom::blink::BroadcastChannelClient,
+                             BroadcastChannel>
+      receiver_;
+  HeapMojoAssociatedRemote<mojom::blink::BroadcastChannelClient> remote_client_;
 
   // Notifies the scheduler that a broadcast channel is active.
   FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle
@@ -117,8 +115,7 @@
   // ConnectToChannel messages (with ordering preserved) to the
   // RenderFrameHostImpl associated with this frame. When a BroadcastChannel is
   // instantiated from a worker execution context, this member is not used.
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedRemote<mojom::blink::BroadcastChannelProvider>
+  HeapMojoAssociatedRemote<mojom::blink::BroadcastChannelProvider>
       associated_remote_;
 };
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
index e3e9203..73e2c9c 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
@@ -113,6 +113,7 @@
 #include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_impl.h"
 #include "third_party/blink/renderer/modules/peerconnection/rtc_void_request_promise_impl.h"
 #include "third_party/blink/renderer/modules/peerconnection/web_rtc_stats_report_callback_resolver.h"
+#include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
@@ -140,6 +141,10 @@
 
 namespace blink {
 
+BASE_FEATURE(kWebRtcLegacyGetStatsThrows,
+             "WebRtcLegacyGetStatsThrows",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 namespace {
 
 const char kSignalingStateClosedMessage[] =
@@ -1659,7 +1664,7 @@
         V8MediaStreamTrack::ToImplWithTypeCheck(isolate,
                                                 legacy_selector.V8Value());
     return LegacyCallbackBasedGetStats(script_state, success_callback,
-                                       selector_or_null);
+                                       selector_or_null, exception_state);
   }
   // Custom binding for spec-compliant
   // "getStats(optional MediaStreamTrack? selector)". null is a valid selector
@@ -1681,7 +1686,8 @@
 ScriptPromise RTCPeerConnection::LegacyCallbackBasedGetStats(
     ScriptState* script_state,
     V8RTCStatsCallback* success_callback,
-    MediaStreamTrack* selector) {
+    MediaStreamTrack* selector,
+    ExceptionState& exception_state) {
   ExecutionContext* context = ExecutionContext::From(script_state);
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   ScriptPromise promise = resolver->Promise();
@@ -1694,12 +1700,20 @@
     UseCounter::Count(context,
                       WebFeature::kRTCPeerConnectionLegacyGetStatsTrial);
   } else {
-    // The deprecation trial is NOT enabled: show a deprecation warning.
-    // TODO(https://crbug.com/822696): In M114 add a base::Feature to control
-    // the throwing of an exception here. The plan is to throw in Canary/Beta in
-    // M114 and to throw in Stable in M117.
+    // The deprecation trial is NOT enabled: show a deprecation warning and
+    // maybe throw an exception.
     Deprecation::CountDeprecation(
         context, WebFeature::kRTCPeerConnectionGetStatsLegacyNonCompliant);
+    // The plan from the Intent to Deprecate is:
+    // - M114: Throw an exception in Canary/Beta.
+    // - M117: Throw in Stable.
+    // Which channel to throw on is controlled via the base::Feature.
+    if (base::FeatureList::IsEnabled(kWebRtcLegacyGetStatsThrows)) {
+      exception_state.ThrowDOMException(
+          DOMExceptionCode::kNotSupportedError,
+          "The callback-based getStats() method is no longer supported.");
+      return ScriptPromise();
+    }
   }
   auto* stats_request = MakeGarbageCollected<RTCStatsRequestImpl>(
       GetExecutionContext(), this, success_callback, selector);
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
index 8f58dd1..dde910e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h
@@ -213,7 +213,8 @@
   ScriptPromise LegacyCallbackBasedGetStats(
       ScriptState*,
       V8RTCStatsCallback* success_callback,
-      MediaStreamTrack* selector);
+      MediaStreamTrack* selector,
+      ExceptionState&);
   ScriptPromise PromiseBasedGetStats(ScriptState*,
                                      MediaStreamTrack* selector,
                                      ExceptionState&);
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.cc b/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.cc
index 857d3f4..1c1c988 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.cc
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.cc
@@ -57,7 +57,8 @@
 SmartCardResourceManager::SmartCardResourceManager(NavigatorBase& navigator)
     : Supplement<NavigatorBase>(navigator),
       ExecutionContextLifecycleObserver(navigator.GetExecutionContext()),
-      service_(navigator.GetExecutionContext()) {}
+      service_(navigator.GetExecutionContext()),
+      receiver_(this, navigator.GetExecutionContext()) {}
 
 void SmartCardResourceManager::ContextDestroyed() {
   CloseServiceConnection();
@@ -121,6 +122,7 @@
 
 void SmartCardResourceManager::Trace(Visitor* visitor) const {
   visitor->Trace(service_);
+  visitor->Trace(receiver_);
   visitor->Trace(get_readers_promises_);
   visitor->Trace(watch_for_readers_promises_);
   visitor->Trace(reader_cache_);
@@ -237,7 +239,7 @@
                     WrapWeakPersistent(this)));
   DCHECK(!receiver_.is_bound());
   service_->RegisterClient(
-      receiver_.BindNewEndpointAndPassRemote(),
+      receiver_.BindNewEndpointAndPassRemote(task_runner),
       WTF::BindOnce(&SmartCardResourceManager::OnServiceClientRegistered,
                     WrapWeakPersistent(this)));
 }
diff --git a/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.h b/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.h
index dcceebb9..34caffa 100644
--- a/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.h
+++ b/third_party/blink/renderer/modules/smart_card/smart_card_resource_manager.h
@@ -5,7 +5,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_SMART_CARD_SMART_CARD_RESOURCE_MANAGER_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_SMART_CARD_SMART_CARD_RESOURCE_MANAGER_H_
 
-#include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/smart_card/smart_card.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
@@ -15,6 +14,7 @@
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_associated_receiver.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/gc_plugin.h"
@@ -79,9 +79,9 @@
   SmartCardReaderPresenceObserver* GetOrCreatePresenceObserver();
 
   HeapMojoRemote<mojom::blink::SmartCardService> service_;
-  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
-  mojo::AssociatedReceiver<mojom::blink::SmartCardServiceClient> receiver_{
-      this};
+  HeapMojoAssociatedReceiver<mojom::blink::SmartCardServiceClient,
+                             SmartCardResourceManager>
+      receiver_;
   HeapHashSet<Member<ScriptPromiseResolver>> get_readers_promises_;
   HeapHashSet<Member<ScriptPromiseResolver>> watch_for_readers_promises_;
   bool tracking_started_ = false;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index edd83fac..3fb2f1db 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2042,6 +2042,10 @@
       status: "stable",
     },
     {
+      name: "LayoutNewSubtreeRoot",
+      status: "stable",
+    },
+    {
       name: "LayoutNewSVGForeignObjectEntry",
       status: "stable",
     },
diff --git a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
index fe2da01..12ed57ee 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_input_handler_manager.cc
@@ -601,26 +601,28 @@
     event->EventPointer()->SetTimeStamp(base::TimeTicks::Now());
   }
 
+  // TODO(b/224960731): Fix tests and add
+  // `DCHECK(!arrived_in_browser_main_timestamp.is_null())`.
+  //  We expect that `arrived_in_browser_main_timestamp` is always
+  //  found, but there are a lot of tests where this component is not set.
+  //  Currently EventMetrics knows how to handle null timestamp, so we
+  //  don't process it here.
+  const base::TimeTicks arrived_in_browser_main_timestamp =
+      event->Event()
+          .GetEventLatencyMetadata()
+          .arrived_in_browser_main_timestamp;
   std::unique_ptr<cc::EventMetrics> metrics;
   if (event->Event().IsGestureScroll()) {
     const auto& gesture_event =
         static_cast<const WebGestureEvent&>(event->Event());
     const bool is_inertial = gesture_event.InertialPhase() ==
                              WebGestureEvent::InertialPhaseState::kMomentum;
-
-    // TODO(b/224960731): It is not recommended to use LatencyInfo. So we need
-    // to create a separate field with "arrived_in_browser_main" timestamp in
-    // WebInputEvent and use it here.
-    base::TimeTicks arrived_in_browser_main_timestamp;
-    event->latency_info().FindLatency(
-        ui::LatencyComponentType::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
-        &(arrived_in_browser_main_timestamp));
-    // TODO(b/224960731): Fix tests and add
-    // `DCHECK(!arrived_in_browser_main_timestamp.is_null())`.
-    //  We expect that `INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT` is always
-    //  found, but there are a lot of tests where this component is not set.
-    //  Currently EventMetrics knows how to handle null timestamp, so we
-    //  don't process it here.
+    //'scrolls_blocking_touch_dispatched_to_renderer' can be null. It is set
+    // by the Browser only if the corresponding TouchMove was blocking.
+    base::TimeTicks blocking_touch_dispatched_to_renderer_timestamp =
+        event->Event()
+            .GetEventLatencyMetadata()
+            .scrolls_blocking_touch_dispatched_to_renderer;
 
     if (gesture_event.GetType() == WebInputEvent::Type::kGestureScrollUpdate) {
       metrics = cc::ScrollUpdateEventMetrics::Create(
@@ -632,13 +634,15 @@
           gesture_event.data.scroll_update.delta_y, event->Event().TimeStamp(),
           arrived_in_browser_main_timestamp,
           base::IdType64<class ui::LatencyInfo>(
-              event->latency_info().trace_id()));
+              event->latency_info().trace_id()),
+          blocking_touch_dispatched_to_renderer_timestamp);
       has_seen_first_gesture_scroll_update_after_begin_ = true;
     } else {
       metrics = cc::ScrollEventMetrics::Create(
           gesture_event.GetTypeAsUiEventType(),
           gesture_event.GetScrollInputType(), is_inertial,
-          event->Event().TimeStamp(), arrived_in_browser_main_timestamp);
+          event->Event().TimeStamp(), arrived_in_browser_main_timestamp,
+          blocking_touch_dispatched_to_renderer_timestamp);
       has_seen_first_gesture_scroll_update_after_begin_ = false;
     }
   } else if (WebInputEvent::IsPinchGestureEventType(event->Event().GetType())) {
@@ -649,7 +653,8 @@
         gesture_event.GetScrollInputType(), event->Event().TimeStamp());
   } else {
     metrics = cc::EventMetrics::Create(event->Event().GetTypeAsUiEventType(),
-                                       event->Event().TimeStamp());
+                                       event->Event().TimeStamp(),
+                                       arrived_in_browser_main_timestamp);
   }
 
   if (uses_input_handler_) {
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 67761ccd..0f49496 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1836,15 +1836,10 @@
 
 crbug.com/498539 http/tests/devtools/tracing/timeline-misc/timeline-bound-function.js [ Failure Pass ]
 
-crbug.com/498539 crbug.com/794869 crbug.com/798548 crbug.com/946716 http/tests/devtools/elements/styles-4/styles-update-from-js.js [ Crash Failure Pass Timeout ]
-
 crbug.com/889952 fast/selectors/selection-window-inactive.html [ Failure Pass ]
 
 crbug.com/1107923 inspector-protocol/debugger/wasm-streaming-url.js [ Failure Pass Timeout ]
 
-# Script let/const redeclaration errors
-crbug.com/1042162 http/tests/inspector-protocol/console/console-let-const-with-api.js [ Failure Pass ]
-
 # Will be re-enabled and rebaselined once we remove the '--enable-file-cookies' flag.
 crbug.com/470482 fast/cookies/local-file-can-set-cookies.html [ Crash Failure Pass Timeout ]
 
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-computed.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-computed.html
index 914d496..4c96ee8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-computed.html
@@ -12,6 +12,9 @@
 test_computed_value("animation-range-end", "COVER 0%", "cover 0%");
 test_computed_value("animation-range-end", "COVER 100%", "cover");
 test_computed_value("animation-range-end", "cover 120%");
+test_computed_value("animation-range-end", "0", "0px");
+test_computed_value("animation-range-end", "120%");
+test_computed_value("animation-range-end", "120px");
 test_computed_value("animation-range-end", "cover 42%");
 test_computed_value("animation-range-end", "cover -42%");
 test_computed_value("animation-range-end", "contain 42%");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-invalid.html
index ec28da2..459cdfd0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-invalid.html
@@ -5,11 +5,8 @@
 <script src="/css/support/parsing-testcommon.js"></script>
 <script>
 test_invalid_value("animation-range-end", "infinite");
-test_invalid_value("animation-range-end", "0");
 test_invalid_value("animation-range-end", "1s 2s");
 test_invalid_value("animation-range-end", "1s / 2s");
-test_invalid_value("animation-range-end", "100px");
-test_invalid_value("animation-range-end", "100%");
 
 test_invalid_value("animation-range-end", "peek 50%");
 test_invalid_value("animation-range-end", "50% contain");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-valid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-valid.html
index 4a248f3..aeeb2ee5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-end-valid.html
@@ -10,6 +10,9 @@
 test_valid_value("animation-range-end", "cover 100%", "cover");
 test_valid_value("animation-range-end", "cover 120%");
 test_valid_value("animation-range-end", "cover 42%");
+test_valid_value("animation-range-end", "0", "0px");
+test_valid_value("animation-range-end", "120%");
+test_valid_value("animation-range-end", "120px");
 test_valid_value("animation-range-end", "cover -42%");
 test_valid_value("animation-range-end", "contain 42%");
 test_valid_value("animation-range-end", "exit 42%");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-shorthand.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-shorthand.html
index e5f6e866..8acf0b1c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-shorthand.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-shorthand.html
@@ -40,6 +40,11 @@
 test_valid_value("animation-range", "entry 50% exit 50%");
 test_valid_value("animation-range",
                  "cover 50% entry 50%, contain 50% exit 50%");
+test_valid_value("animation-range", "50% exit 50%");
+test_valid_value("animation-range", "normal 100px");
+test_valid_value("animation-range", "100px");
+test_valid_value("animation-range", "100px normal", "100px");
+test_valid_value("animation-range", "10% normal", "10%");
 
 test_computed_value("animation-range", "normal");
 test_computed_value("animation-range", "normal normal", "normal");
@@ -75,6 +80,11 @@
                  "cover 50% entry 50%, contain 50% exit 50%");
 
 test_computed_value("animation-range", "entry 10em exit 20em", "entry 100px exit 200px");
+test_computed_value("animation-range", "10em exit 20em", "100px exit 200px");
+test_computed_value("animation-range", "normal 100px");
+test_computed_value("animation-range", "100px");
+test_computed_value("animation-range", "100px normal", "100px");
+test_computed_value("animation-range", "10% normal", "10%");
 
 test_invalid_value("animation-range", "entry 50% 0s", "entry 50%");
 test_invalid_value("animation-range", "0s entry 50%");
@@ -157,4 +167,12 @@
   'animation-range-start': 'exit calc(10% + 50px)',
   'animation-range-end': 'exit',
 });
+test_shorthand_value('animation-range', '100px', {
+  'animation-range-start': '100px',
+  'animation-range-end': 'normal',
+});
+test_shorthand_value('animation-range', '10%', {
+  'animation-range-start': '10%',
+  'animation-range-end': 'normal',
+});
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-computed.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-computed.html
index 9750fb50..044aea2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-computed.html
@@ -13,6 +13,9 @@
 test_computed_value("animation-range-start", "COVER 100%", "cover 100%");
 test_computed_value("animation-range-start", "cover 120%");
 test_computed_value("animation-range-start", "cover 42%");
+test_computed_value("animation-range-start", "0", "0px");
+test_computed_value("animation-range-start", "120%");
+test_computed_value("animation-range-start", "120px");
 test_computed_value("animation-range-start", "cover -42%");
 test_computed_value("animation-range-start", "contain 42%");
 test_computed_value("animation-range-start", "exit 42%");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-valid.html b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-valid.html
index d70a371..309f4cc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/parsing/animation-range-start-valid.html
@@ -10,6 +10,9 @@
 test_valid_value("animation-range-start", "cover 100%");
 test_valid_value("animation-range-start", "cover 120%");
 test_valid_value("animation-range-start", "cover 42%");
+test_valid_value("animation-range-start", "0", "0px");
+test_valid_value("animation-range-start", "120%");
+test_valid_value("animation-range-start", "120px");
 test_valid_value("animation-range-start", "cover -42%");
 test_valid_value("animation-range-start", "contain 42%");
 test_valid_value("animation-range-start", "exit 42%");
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/crashtests/chrome-bug-1429955-crash.html b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/crashtests/chrome-bug-1429955-crash.html
new file mode 100644
index 0000000..bdf4002
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/container-queries/crashtests/chrome-bug-1429955-crash.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/1429955.html">
+<div id="mc" style="display:list-item; width:0; columns:2; container-type:size;">
+  <div id="abs" style="position:absolute; container-type:inline-size;">line</div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-ignored.tentative.html
index 32cb89c..54a6257f 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-ignored.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-ignored.tentative.html
@@ -135,11 +135,11 @@
     await waitForNextFrame();
     assert_equals(getComputedStyle(element).width, '120px');
     element.getAnimations()[0].timeline = null;
-    assert_equals(getComputedStyle(element).width, '0px');
+    assert_equals(getComputedStyle(element).width, '120px');
 
     // Changing the animation-timeline property should have no effect.
     element.style = 'animation-timeline:timeline2';
-    assert_equals(getComputedStyle(element).width, '0px');
+    assert_equals(getComputedStyle(element).width, '120px');
   }, 'animation-timeline ignored after setting timeline with JS (null)');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
index 30461723..ed2c32d 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-timeline-named-scroll-progress-timeline.tentative.html
@@ -7,6 +7,7 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/web-animations/testcommon.js"></script>
 <script src="support/testcommon.js"></script>
+<script src="/scroll-animations/scroll-timelines/testcommon.js"></script>
 <style>
   @keyframes anim {
     from { translate: 50px; }
@@ -318,17 +319,24 @@
   await waitForCSSScrollTimelineStyle();
 
   assert_equals(getComputedStyle(target).translate, '100px');
+  const anim = target.getAnimations()[0];
+  assert_percents_equal(anim.startTime, 0);
+  assert_percents_equal(anim.currentTime, 50);
 
   // This effectively removes the CSS-created ScrollTimeline on this element,
   // thus invoking "setting the timeline of an animation" [1] with a null-
-  // timeline on affected elements. This in turn causes the current time to
-  // become unresolved [2], ultimately resulting in no effect value.
-  //
-  // [1] https://drafts.csswg.org/web-animations-1/#setting-the-timeline
-  // [2] https://drafts.csswg.org/web-animations-1/#the-current-time-of-an-animation
+  // timeline on affected elements. This in turn runs the procedure to set the
+  // current time to previous progress * end time. Ultimately, this sets the
+  // hold time of the animation.
+
+  // [1] https://www.w3.org/TR/web-animations-2/#setting-the-timeline
+  // [2] https://www.w3.org/TR/web-animations-2/
+  //     #setting-the-current-time-of-an-animation
   scroller.remove();
   await waitForNextFrame();
-  assert_equals(getComputedStyle(target).translate, 'none');
+  assert_equals(getComputedStyle(target).translate, '100px');
+  assert_equals(anim.startTime, null);
+  assert_times_equal(anim.currentTime, 5000);
 }, 'scroll-timeline-name on removed element affects subsequent siblings');
 
 promise_test(async t => {
@@ -371,13 +379,19 @@
   await waitForCSSScrollTimelineStyle();
 
   assert_equals(getComputedStyle(target).translate, '100px');
+  const anim = target.getAnimations()[0];
+  assert_percents_equal(anim.startTime, 0);
+  assert_percents_equal(anim.currentTime, 50);
 
   // See comment in the test "scroll-timeline-name on removed element ..." for
   // an explantation of this result. (Setting display:none is similar to
   // removing the element).
   scroller.style.display = 'none';
   await waitForNextFrame();
-  assert_equals(getComputedStyle(target).translate, 'none');
+  assert_equals(getComputedStyle(target).translate, '100px');
+  assert_equals(anim.startTime, null);
+  assert_times_equal(anim.currentTime, 5000);
+
 }, 'scroll-timeline-name on element becoming display:none affects subsequent siblings');
 
 promise_test(async t => {
@@ -461,13 +475,16 @@
   assert_true(!!anim, 'Failed to create animation');
   assert_true(!!anim.timeline, 'Failed to create timeline');
   assert_equals(getComputedStyle(target).translate, '100px');
+  assert_percents_equal(anim.startTime, 0);
+  assert_percents_equal(anim.currentTime, 50);
 
   scroller.style.scrollTimelineName = 'timeline-B';
   await waitForNextFrame();
 
   assert_equals(anim.timeline, null, 'Failed to remove timeline');
-  assert_equals(getComputedStyle(target).translate, 'none');
-
+  assert_equals(getComputedStyle(target).translate, '100px');
+  assert_equals(anim.startTime, null);
+  assert_times_equal(anim.currentTime, 5000);
 }, 'Change in scroll-timeline-name to no longer match animation timeline updates animation.');
 
 promise_test(async t => {
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-update-ref.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-update-ref.html
new file mode 100644
index 0000000..7e375a1d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/animation-update-ref.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Reference file for various tests that update an animation with a scroll timeline</title>
+<script src="/web-animations/testcommon.js"></script>
+</head>
+<style type="text/css">
+  #scroller {
+    border:  1px solid black;
+    overflow: hidden;
+    width: 300px;
+    height: 200px;
+  }
+  #target {
+    margin-bottom: 800px;
+    margin-top:  800px;
+    margin-left:  10px;
+    margin-right:  10px;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+    background-color: green;
+  }
+</style>
+<body>
+  <div id="scroller">
+    <div id="target"></div>
+  </div>
+</body>
+<script type="text/javascript">
+  document.documentElement.addEventListener('TestRendered', async () => {
+    runTest();
+  }, { once: true });
+
+  async function runTest() {
+    // Defaults to exit 60% if using a view timeline with subject = target.
+    const DEFAULT_SCROLL_POS = 860;
+    await waitForCompositorReady();
+
+    const urlParams = new URLSearchParams(window.location.search);
+    target.style.transform =
+        `translateX(${urlParams.get('translate') || "0px"}`;
+
+    scroller.scrollTop = urlParams.get('scroll') || DEFAULT_SCROLL_POS;
+    await waitForNextFrame();
+    await waitForNextFrame();
+
+    // Make sure change to animation range was properly picked up.
+    document.documentElement.classList.remove("reftest-wait");
+  }
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative-expected.txt
deleted file mode 100644
index 5714521..0000000
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative-expected.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-This is a testharness.js-based test.
-PASS Switching between document and scroll timelines [immediate]
-PASS Switching between document and scroll timelines [scroll]
-PASS Switching pending animation from document to scroll timelines [immediate]
-PASS Switching pending animation from document to scroll timelines [scroll]
-PASS Changing computed value of animation-timeline changes effective timeline [immediate]
-PASS Changing computed value of animation-timeline changes effective timeline [scroll]
-FAIL Changing to/from animation-timeline:none [immediate] assert_equals: expected "120px" but got "0px"
-FAIL Changing to/from animation-timeline:none [scroll] assert_equals: expected "120px" but got "0px"
-PASS Changing scroll-timeline on preceding elements affects target element [immediate]
-PASS Changing scroll-timeline on preceding elements affects target element [scroll]
-PASS Reverse animation direction [immediate]
-PASS Reverse animation direction [scroll]
-PASS Switching timelines while paused [immediate]
-PASS Switching timelines while paused [scroll]
-PASS Switching timelines and pausing at the same time [immediate]
-PASS Switching timelines and pausing at the same time [scroll]
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
index b0880a7c..0d951e7 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-dynamic.tentative.html
@@ -159,6 +159,10 @@
     // DocumentTimeline applies by default.
     await assert_width(element, '100px');
 
+    // Wait for the animation to be ready so that we a start time and no hold
+    // time.
+    await element.getAnimations()[0].ready;
+
     // DocumentTimeline -> none
     element.style.animationTimeline = 'none';
     await assert_width(element, '0px');
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-update-reversed-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-update-reversed-animation.html
new file mode 100644
index 0000000..93ad6916
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/scroll-timeline-update-reversed-animation.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Attach a scroll timeline to a reversed animation refTest</title>
+<link rel="help" src="https://www.w3.org/TR/scroll-animations-1/#scroll-timeline-name">
+<link rel="match" href="./animation-update-ref.html?translate=55px&scroll=825">
+<script src="/web-animations/testcommon.js"></script>
+</head>
+<style type="text/css">
+  @keyframes anim {
+    from { transform: translateX(100px) }
+    to { transform: translateX(0px) }
+  }
+  #scroller {
+    border: 1px solid black;
+    overflow: hidden;
+    width: 300px;
+    height: 200px;
+    scroll-timeline: timeline;
+  }
+  #target {
+    margin-bottom: 800px;
+    margin-top: 800px;
+    margin-left: 10px;
+    margin-right: 10px;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+    background-color: green;
+    animation: anim 10s linear paused;
+  }
+  #target.update {
+    animation-play-state: running;
+    animation-timeline:  timeline;
+    animation-duration:  auto;
+  }
+</style>
+<body>
+  <div id="scroller">
+    <div id="target"></div>
+  </div>
+</body>
+<script type="text/javascript">
+  document.documentElement.addEventListener('TestRendered', async () => {
+    runTest();
+  }, { once: true });
+
+  async function runTest() {
+    await waitForCompositorReady();
+
+    const anim = target.getAnimations()[0];
+    anim.playbackRate = -1;
+    await anim.ready;
+
+    // Scroll to 55% of maximum scroll while paused.
+    scroller.scrollTop = 825;
+    await waitForNextFrame();
+
+    target.classList.add('update');
+    await waitForNextFrame();
+
+    // Make sure change to animation range was properly picked up.
+    document.documentElement.classList.remove("reftest-wait");
+  }
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
index 74da8850..76a30ad 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-dynamic.html
@@ -178,6 +178,10 @@
     await scrollTop(scroller, 50);
     assert_equals(getComputedStyle(target).zIndex, '25');
     timeline.style.display = 'none';
-    assert_equals(getComputedStyle(target).zIndex, '-1');
+    // Animation is held at previous current time.
+    assert_equals(getComputedStyle(target).zIndex, '25');
+    const anim = target.getAnimations()[0];
+    assert_equals(anim.startTime, null);
+    assert_times_equal(anim.currentTime, 250);
   }, 'Element with view-timeline becoming display:none');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update-reversed-animation.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update-reversed-animation.html
new file mode 100644
index 0000000..c719916
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update-reversed-animation.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Update timeline range on reversed animation refTest</title>
+<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
+<link rel="match" href="./animation-update-ref.html?translate=60px">
+<script src="/web-animations/testcommon.js"></script>
+</head>
+<style type="text/css">
+  @keyframes anim {
+    from { transform: translateX(100px) }
+    to { transform: translateX(0px) }
+  }
+  #scroller {
+    border:  1px solid black;
+    overflow: hidden;
+    width: 300px;
+    height: 200px;
+  }
+  #target {
+    margin-bottom: 800px;
+    margin-top: 800px;
+    margin-left: 10px;
+    margin-right: 10px;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+    background-color: green;
+    animation: anim auto linear;
+    animation-timeline: timeline;
+    view-timeline: timeline;
+  }
+  #target.exit-range {
+    animation-range-start: exit 0%;
+    animation-range-end:  exit 100%;
+  }
+</style>
+<body>
+  <div id="scroller">
+    <div id="target"></div>
+  </div>
+</body>
+<script type="text/javascript">
+  document.documentElement.addEventListener('TestRendered', async () => {
+    runTest();
+  }, { once: true });
+
+  async function runTest() {
+    await waitForCompositorReady();
+
+    const anim = target.getAnimations()[0];
+    anim.playbackRate = -1;
+
+    // Scroll to exit 60%.
+    scroller.scrollTop = 860;
+    await waitForNextFrame();
+
+    // Update the animation range.
+    target.classList.add('exit-range');
+    await waitForNextFrame();
+
+    // Make sure change to animation range was properly picked up.
+    document.documentElement.classList.remove("reftest-wait");
+  }
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update.html b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update.html
new file mode 100644
index 0000000..e8e761d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/css/view-timeline-range-update.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Update timeline range refTest</title>
+<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#named-timeline-range">
+<link rel="match" href="./animation-update-ref.html?translate=40px">
+<script src="/web-animations/testcommon.js"></script>
+</head>
+<style type="text/css">
+  @keyframes anim {
+    from { transform: translateX(100px) }
+    to { transform: translateX(0px) }
+  }
+  #scroller {
+    border:  1px solid black;
+    overflow: hidden;
+    width: 300px;
+    height: 200px;
+  }
+  #target {
+    margin-bottom: 800px;
+    margin-top: 800px;
+    margin-left: 10px;
+    margin-right: 10px;
+    width: 100px;
+    height: 100px;
+    z-index: -1;
+    background-color: green;
+    animation: anim auto linear;
+    animation-timeline: timeline;
+    view-timeline: timeline;
+  }
+  #target.exit-range {
+    animation-range-start: exit 0%;
+    animation-range-end:  exit 100%;
+  }
+</style>
+<body>
+  <div id="scroller">
+    <div id="target"></div>
+  </div>
+</body>
+<script type="text/javascript">
+  document.documentElement.addEventListener('TestRendered', async () => {
+    runTest();
+  }, { once: true });
+
+  async function runTest() {
+    await waitForCompositorReady();
+
+    const anim = target.getAnimations()[0];
+
+    // Scroll to exit 60%.
+    scroller.scrollTop = 860;
+    await waitForNextFrame();
+
+    // Update the animation range.
+    target.classList.add('exit-range');
+    await waitForNextFrame();
+
+    // Make sure change to animation range was properly picked up.
+    document.documentElement.classList.remove("reftest-wait");
+  }
+</script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html
index 69b40cb..34d9af2 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/scroll-timelines/setting-timeline.tentative.html
@@ -255,12 +255,14 @@
   await animation.ready;
   await updateScrollPosition(scrollTimeline, 100);
 
-  assert_equals(animation.playState, 'running');
+  const progress = animation.currentTime.value / 100;
+  const duration = animation.effect.getTiming().duration;
   animation.timeline = null;
 
-  assert_equals(animation.playState, 'running');
+  const expectedCurrentTime = progress * duration;
+  assert_times_equal(animation.currentTime, expectedCurrentTime);
 }, 'Transitioning from a scroll timeline to a null timeline on a running ' +
-    'animation preserves the play state');
+    'animation preserves current progress.');
 
 promise_test(async t => {
   const keyframeEfect = new KeyframeEffect(createDiv(t),
diff --git a/third_party/blink/web_tests/external/wpt/scroll-animations/view-timelines/change-animation-range-updates-play-state.html b/third_party/blink/web_tests/external/wpt/scroll-animations/view-timelines/change-animation-range-updates-play-state.html
index ecc80aea..53330d32 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-animations/view-timelines/change-animation-range-updates-play-state.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-animations/view-timelines/change-animation-range-updates-play-state.html
@@ -55,16 +55,21 @@
       anim.rangeStart = 'contain 0%';  // 700px
       anim.rangeEnd = 'contain 100%';  // 800px
       assert_equals(anim.playState, 'running');
+      assert_percents_equal(anim.currentTime, 100/6);
 
       // Animation in the after phase and switches to the finished state.
       anim.rangeStart = 'entry 0%';  // 600px
       anim.rangeEnd = 'entry 100%';  // 700px
       assert_equals(anim.playState, 'finished');
+      // Clamp to effect end when finished.
+      assert_percents_equal(anim.currentTime, 100/3);
 
       // Animation in the before phase and switches back to the running state.
       anim.rangeStart = 'exit 0%';  // 800px
       anim.rangeEnd = 'exit 100%';  // 900px
       assert_equals(anim.playState, 'running');
+      assert_percents_equal(anim.currentTime, -100/6);
+
     }, 'Changing the animation range updates the play state');
   }
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js-expected.txt
index 006af98..c3f86af 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js-expected.txt
@@ -9,18 +9,18 @@
 element.style { ()
     color: #daC0DE;
     border: 1px solid black;
-        border-top-color: black;
-        border-top-style: solid;
         border-top-width: 1px;
-        border-right-color: black;
-        border-right-style: solid;
         border-right-width: 1px;
-        border-bottom-color: black;
-        border-bottom-style: solid;
         border-bottom-width: 1px;
-        border-left-color: black;
-        border-left-style: solid;
         border-left-width: 1px;
+        border-top-style: solid;
+        border-right-style: solid;
+        border-bottom-style: solid;
+        border-left-style: solid;
+        border-top-color: black;
+        border-right-color: black;
+        border-bottom-color: black;
+        border-left-color: black;
         border-image-source: initial;
         border-image-slice: initial;
         border-image-width: initial;
@@ -49,18 +49,18 @@
 element.style { ()
     color: rgb(192, 255, 238);
     border: 3px dashed green;
-        border-top-color: green;
-        border-top-style: dashed;
         border-top-width: 3px;
-        border-right-color: green;
-        border-right-style: dashed;
         border-right-width: 3px;
-        border-bottom-color: green;
-        border-bottom-style: dashed;
         border-bottom-width: 3px;
-        border-left-color: green;
-        border-left-style: dashed;
         border-left-width: 3px;
+        border-top-style: dashed;
+        border-right-style: dashed;
+        border-bottom-style: dashed;
+        border-left-style: dashed;
+        border-top-color: green;
+        border-right-color: green;
+        border-bottom-color: green;
+        border-left-color: green;
         border-image-source: initial;
         border-image-slice: initial;
         border-image-width: initial;
@@ -87,7 +87,7 @@
 
 ======== Inherited from div#container.red ========
 [expanded] 
-Style Attribute { ()
+style attribute { ()
     color: rgb(192, 255, 238);
 
 
@@ -106,7 +106,7 @@
 
 ======== Inherited from div#container.red ========
 [expanded] 
-Style Attribute { ()
+style attribute { ()
     color: rgb(192, 255, 238);
 
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js.js b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js.js
index a760ceb..fd352a4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/styles-4/styles-update-from-js.js
@@ -49,52 +49,43 @@
       }
   `);
 
-  TestRunner.runTestSuite([
-    function testInit(next) {
-      ElementsTestRunner.selectNodeAndWaitForStyles('container', next);
+  TestRunner.runAsyncTestSuite([
+    async function testInit(next) {
+      await ElementsTestRunner.selectNodeAndWaitForStylesPromise('container');
     },
 
-    function testSetStyleAttribute(next) {
-      waitAndDumpAttributeAndStyles(next);
-      TestRunner.evaluateInPage('modifyStyleAttribute()');
+    async function testSetStyleAttribute() {
+      await TestRunner.evaluateInPage('modifyStyleAttribute()');
+      await waitAndDumpAttributeAndStyles();
     },
 
-    function testSetStyleCSSText(next) {
-      waitAndDumpAttributeAndStyles(next);
-      TestRunner.evaluateInPage('modifyCSSText()');
+    async function testSetStyleCSSText() {
+      await TestRunner.evaluateInPage('modifyCSSText()');
+      await waitAndDumpAttributeAndStyles();
     },
 
-    function testSetViaParsedAttributes(next) {
-      waitAndDumpAttributeAndStyles(next);
-      TestRunner.evaluateInPage('modifyParsedAttributes()');
+    async function testSetViaParsedAttributes() {
+      await TestRunner.evaluateInPage('modifyParsedAttributes()');
+      await waitAndDumpAttributeAndStyles();
     },
 
-    function testSetViaAncestorClass(next) {
-      ElementsTestRunner.selectNodeAndWaitForStyles('child', callback);
-
-      function callback() {
-        waitAndDumpAttributeAndStyles(next, 'child');
-        TestRunner.evaluateInPage('modifyContainerClass()');
-      }
+    async function testSetViaAncestorClass() {
+      await ElementsTestRunner.selectNodeAndWaitForStylesPromise('child');
+      await TestRunner.evaluateInPage('modifyContainerClass()');
+      await waitAndDumpAttributeAndStyles('child');
     },
 
-    function testSetViaSiblingAttr(next) {
-      ElementsTestRunner.selectNodeAndWaitForStyles('childSibling', callback);
-
-      function callback() {
-        waitAndDumpAttributeAndStyles(next, 'childSibling');
-        TestRunner.evaluateInPage('modifyChildAttr()');
-      }
+    async function testSetViaSiblingAttr() {
+      await ElementsTestRunner.selectNodeAndWaitForStylesPromise('childSibling');
+      await TestRunner.evaluateInPage('modifyChildAttr()');
+      await waitAndDumpAttributeAndStyles('childSibling');
     }
   ]);
 
-  function waitAndDumpAttributeAndStyles(next, id) {
+  async function waitAndDumpAttributeAndStyles(id) {
     id = id || 'container';
-    async function callback() {
-      await dumpAttributeAndStyles(id);
-      next();
-    }
-    ElementsTestRunner.waitForStyles(id, callback);
+    await new Promise(resolve => ElementsTestRunner.waitForStyles(id, resolve));
+    await dumpAttributeAndStyles(id);
   }
 
   async function dumpAttributeAndStyles(id) {
diff --git a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-layout/timeline-layout-expected.txt b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-layout/timeline-layout-expected.txt
index 6f3cc681..a07f1fd 100644
--- a/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-layout/timeline-layout-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/tracing/timeline-layout/timeline-layout-expected.txt
@@ -42,6 +42,44 @@
 {
     data : {
         beginData : {
+            dirtyObjects : 3
+            frame : <string>
+            partialLayout : false
+            stackTrace : <object>
+            totalObjects : 22
+        }
+        endData : {
+            layoutRoots : [
+                {
+                    depth : 1
+                    nodeId : <number>
+                    quads : [
+                        [
+                            0
+                            0
+                            800
+                            0
+                            800
+                            600
+                            0
+                            600
+                        ]
+                    ]
+                }
+            ]
+        }
+    }
+    endTime : <number>
+    frameId : <string>
+    stackTrace : <object>
+    startTime : <number>
+    type : "Layout"
+}
+Text details for Layout: test://evaluations/0/timeline-layout.js:40:32
+Layout Properties:
+{
+    data : {
+        beginData : {
             dirtyObjects : 2
             frame : <string>
             partialLayout : true
@@ -76,4 +114,42 @@
     type : "Layout"
 }
 Text details for Layout: test://evaluations/0/timeline-layout.js:40:32
+Layout Properties:
+{
+    data : {
+        beginData : {
+            dirtyObjects : 3
+            frame : <string>
+            partialLayout : false
+            stackTrace : <object>
+            totalObjects : 22
+        }
+        endData : {
+            layoutRoots : [
+                {
+                    depth : 1
+                    nodeId : <number>
+                    quads : [
+                        [
+                            0
+                            0
+                            800
+                            0
+                            800
+                            600
+                            0
+                            600
+                        ]
+                    ]
+                }
+            ]
+        }
+    }
+    endTime : <number>
+    frameId : <string>
+    stackTrace : <object>
+    startTime : <number>
+    type : "Layout"
+}
+Text details for Layout: test://evaluations/0/timeline-layout.js:40:32
 
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/console/console-let-const-with-api-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/console/console-let-const-with-api-expected.txt
index 06f4097..1d8510c3 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/console/console-let-const-with-api-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/console/console-let-const-with-api-expected.txt
@@ -3,24 +3,24 @@
 second 'let a = 1;' result: wasThrown = true
 exception message: Uncaught SyntaxError: Identifier 'a' has already been declared
 {"result":{"type":"number","value":42,"description":"42"}}
-function $(selector, [startNode]) { [Command Line API] }
-function $$(selector, [startNode]) { [Command Line API] }
-function $x(xpath, [startNode]) { [Command Line API] }
-function dir(value) { [Command Line API] }
-function dirxml(value) { [Command Line API] }
-function keys(object) { [Command Line API] }
-function values(object) { [Command Line API] }
-function profile(title) { [Command Line API] }
-function profileEnd(title) { [Command Line API] }
-function monitorEvents(object, [types]) { [Command Line API] }
-function unmonitorEvents(object, [types]) { [Command Line API] }
-function inspect(object) { [Command Line API] }
-function copy(value) { [Command Line API] }
-function clear() { [Command Line API] }
-function getEventListeners(node) { [Command Line API] }
-function debug(function, condition) { [Command Line API] }
-function undebug(function) { [Command Line API] }
-function monitor(function) { [Command Line API] }
-function unmonitor(function) { [Command Line API] }
-function table(data, [columns]) { [Command Line API] }
+function $() { [native code] }
+function $$() { [native code] }
+function $x() { [native code] }
+function dir() { [native code] }
+function dirxml() { [native code] }
+function keys() { [native code] }
+function values() { [native code] }
+function profile() { [native code] }
+function profileEnd() { [native code] }
+function monitorEvents() { [native code] }
+function unmonitorEvents() { [native code] }
+function inspect() { [native code] }
+function copy() { [native code] }
+function clear() { [native code] }
+function getEventListeners() { [native code] }
+function debug() { [native code] }
+function undebug() { [native code] }
+function monitor() { [native code] }
+function unmonitor() { [native code] }
+function table() { [native code] }
 
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index fe5bd5d98..cd6b733 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -19126,6 +19126,23 @@
   </description>
 </action>
 
+<action name="MobileInactiveTabsSettingsBack">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when user goes back from Inactive Tabs Settings UI to Tabs Settings
+    screen. iOS only.
+  </description>
+</action>
+
+<action name="MobileInactiveTabsSettingsClose">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when Inactive Tabs Settings UI was dismissed. iOS only.
+  </description>
+</action>
+
 <action name="MobileIncognitoBiometricAuthenticationRequested">
   <owner>stkhapugin@chromium.org</owner>
   <description>
@@ -21730,6 +21747,23 @@
   </description>
 </action>
 
+<action name="MobileTabsSettingsBack">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when user goes back from Tabs Settings UI to root Settings screen.
+    iOS only.
+  </description>
+</action>
+
+<action name="MobileTabsSettingsClose">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when Tabs Settings UI was dismissed. iOS only.
+  </description>
+</action>
+
 <action name="MobileTabStripCloseTab">
   <obsolete>Deprecated as of 5/2015</obsolete>
   <owner>Please list the metric's owners. Add more owner tags as needed.</owner>
@@ -29586,6 +29620,22 @@
   </description>
 </action>
 
+<action name="Settings.Tabs">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when user navigates to Tabs Settings. iOS only.
+  </description>
+</action>
+
+<action name="Settings.Tabs.InactiveTabs">
+  <owner>alionadangla@chromium.org</owner>
+  <owner>lpromero@chromium.org</owner>
+  <description>
+    Reported when user navigates to Inactive Tabs Settings. iOS only.
+  </description>
+</action>
+
 <action name="Settings.VoiceSearch">
   <owner>rohitrao@chromium.org</owner>
   <owner>sczs@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e6fcccb..b446a9f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26010,6 +26010,54 @@
   <int value="3" label="Cancel"/>
 </enum>
 
+<enum name="DevicePoliciesState">
+  <int value="0" label="Consumer owned good key and policy"/>
+  <int value="1" label="Consumer owned malformed key, good policy"/>
+  <int value="2" label="Consumer owned no key, good policy"/>
+  <int value="3" label="Consumer owned good key, malformed policy"/>
+  <int value="4" label="Consumer owned malformed key, malformed policy"/>
+  <int value="5" label="Consumer owned no key, malformed policy"/>
+  <int value="6" label="Consumer owned good key, no policy"/>
+  <int value="7" label="Consumer owned malformed key, no policy"/>
+  <int value="8" label="Consumer owned no key, no policy"/>
+  <int value="9" label="Enrolled device good key and policy"/>
+  <int value="10" label="Enrolled device malformed key, good policy"/>
+  <int value="11" label="Enrolled device no key, good policy"/>
+  <int value="12" label="Enrolled device good key, malformed policy"/>
+  <int value="13" label="Enrolled device malformed key, malformed policy"/>
+  <int value="14" label="Enrolled device no key, malformed policy"/>
+  <int value="15" label="Enrolled device good key, no policy"/>
+  <int value="16" label="Enrolled device malformed key, no policy"/>
+  <int value="17" label="Enrolled device no key, no policy"/>
+  <int value="18" label="Legacy retail good key and policy"/>
+  <int value="19" label="Legacy retail malformed key, good policy"/>
+  <int value="20" label="Legacy retail no key, good policy"/>
+  <int value="21" label="Legacy retail good key, malformed policy"/>
+  <int value="22" label="Legacy retail malformed key, malformed policy"/>
+  <int value="23" label="Legacy retail no key, malformed policy"/>
+  <int value="24" label="Legacy retail good key, no policy"/>
+  <int value="25" label="Legacy retail malformed key, no policy"/>
+  <int value="26" label="Legacy retail no key, no policy"/>
+  <int value="27" label="Consumer kiosk good key and policy"/>
+  <int value="28" label="Consumer kiosk malformed key, good policy"/>
+  <int value="29" label="Consumer kiosk no key, good policy"/>
+  <int value="30" label="Consumer kiosk good key, malformed policy"/>
+  <int value="31" label="Consumer kiosk malformed key, malformed policy"/>
+  <int value="32" label="Consumer kiosk no key, malformed policy"/>
+  <int value="33" label="Consumer kiosk good key, no policy"/>
+  <int value="34" label="Consumer kiosk malformed key, no policy"/>
+  <int value="35" label="Consumer kiosk no key, no policy"/>
+  <int value="36" label="Unknown owner good key and policy"/>
+  <int value="37" label="Unknown owner malformed key, good policy"/>
+  <int value="38" label="Unknown owner no key, good policy"/>
+  <int value="39" label="Unknown owner good key, malformed policy"/>
+  <int value="40" label="Unknown owner malformed key, malformed policy"/>
+  <int value="41" label="Unknown owner no key, malformed policy"/>
+  <int value="42" label="Unknown owner good key, no policy"/>
+  <int value="43" label="Unknown owner malformed key, no policy"/>
+  <int value="44" label="Unknown owner no key, no policy"/>
+</enum>
+
 <enum name="DeviceSettingsFeatureFlagsMigrationStatus">
   <int value="0" label="No feature flags present"/>
   <int value="1" label="Migration already completed previously"/>
@@ -27566,21 +27614,6 @@
   <int value="2" label="Mobile-friendly distillable"/>
 </enum>
 
-<enum name="DistillableType2">
-  <int value="0" label="Non-mobile-friendly, not distillable"/>
-  <int value="1" label="Mobile-friendly, not distillable"/>
-  <int value="2" label="Non-mobile-friendly, distillable"/>
-  <int value="3" label="Mobile-friendly, distillable"/>
-</enum>
-
-<enum name="DistillRejection">
-  <int value="0" label="Not an article"/>
-  <int value="1" label="Mobile-friendly"/>
-  <int value="2" label="Domain is filtered"/>
-  <int value="3" label="Predicted to be short"/>
-  <int value="4" label="Not rejected"/>
-</enum>
-
 <enum name="DlcService.InstallResult">
   <int value="0" label="Unknown error"/>
   <int value="1" label="Success - New install"/>
@@ -82612,17 +82645,6 @@
   <int value="443" label="Port 443"/>
 </enum>
 
-<enum name="PostMergeVerificationOutcome">
-  <int value="0" label="Undefined"/>
-  <int value="1" label="Succeeded"/>
-  <int value="2" label="No accounts found"/>
-  <int value="3" label="Missing primary account"/>
-  <int value="4" label="Primary account is not the first"/>
-  <int value="5" label="Verification failed"/>
-  <int value="6" label="Connection failed"/>
-  <int value="7" label="Overflow"/>
-</enum>
-
 <enum name="PostOperationState">
   <obsolete>
     Removed in M95.
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 98d31a7..78799bb 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -2001,32 +2001,6 @@
   </token>
 </histogram>
 
-<histogram name="DomDistiller.DistillabilityRejection" enum="DistillRejection"
-    expires_after="M85">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    The reason to reject distillability at PageDistillableAfterParsing time.
-  </summary>
-</histogram>
-
-<histogram name="DomDistiller.DistillabilityScoreNMF.Negative" units="score"
-    expires_after="M77">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Score of distillability from AdaBoost model, non-mobile-friendly only. The
-    score shown here is multiplied by 100.
-  </summary>
-</histogram>
-
-<histogram name="DomDistiller.DistillabilityScoreNMF.Positive" units="score"
-    expires_after="M77">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Score of distillability from AdaBoost model, non-mobile-friendly only. The
-    score shown here is multiplied by 100.
-  </summary>
-</histogram>
-
 <histogram name="DomDistiller.InfoBarUsage" enum="BooleanUsage"
     expires_after="2023-06-25">
   <obsolete>
@@ -2042,24 +2016,6 @@
   </summary>
 </histogram>
 
-<histogram name="DomDistiller.LongArticleScoreNMF.Negative" units="score"
-    expires_after="M85">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Score of long article from AdaBoost model, distillable and
-    non-mobile-friendly only. The score shown here is multiplied by 100.
-  </summary>
-</histogram>
-
-<histogram name="DomDistiller.LongArticleScoreNMF.Positive" units="score"
-    expires_after="M85">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Score of long article from AdaBoost model, distillable and
-    non-mobile-friendly only. The score shown here is multiplied by 100.
-  </summary>
-</histogram>
-
 <histogram name="DomDistiller.MessageDismissalCondition"
     enum="ReaderModeMessageDismissalCondition" expires_after="2023-09-10">
   <owner>twellington@chromium.org</owner>
@@ -2071,26 +2027,6 @@
   </summary>
 </histogram>
 
-<histogram name="DomDistiller.PageDistillableAfterLoading"
-    enum="DistillableType2" expires_after="M85">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Records the &quot;Distillable Type&quot; (mobile-friendly not distillable,
-    mobile-friendly distillable, non-mobile-friendly not distillable,
-    non-mobile-friendly distillable) for each analyzed page after loading.
-  </summary>
-</histogram>
-
-<histogram name="DomDistiller.PageDistillableAfterParsing"
-    enum="DistillableType2" expires_after="M85">
-  <owner>wychen@chromium.org</owner>
-  <summary>
-    Records the &quot;Distillable Type&quot; (mobile-friendly not distillable,
-    mobile-friendly distillable, non-mobile-friendly not distillable,
-    non-mobile-friendly distillable) for each analyzed page after parsing.
-  </summary>
-</histogram>
-
 <histogram name="DomDistiller.PageHasDistilledData"
     enum="BooleanHasDistilledData" expires_after="M85">
   <owner>kuan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 8a9718d..f8ca04c 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -1150,7 +1150,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.CaptureModeInitWarned" enum="BooleanWarned"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1354,7 +1354,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.PrintingWarnProceeded" enum="Boolean"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1364,7 +1364,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.PrintingWarnSilentProceeded" enum="Boolean"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1474,7 +1474,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ScreenShareWarned" enum="BooleanWarned"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1484,7 +1484,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ScreenShareWarnProceeded" enum="Boolean"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1494,7 +1494,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ScreenShareWarnSilentProceeded" enum="Boolean"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -1526,7 +1526,7 @@
 </histogram>
 
 <histogram name="Enterprise.Dlp.ScreenshotWarnProceeded" enum="Boolean"
-    expires_after="2023-06-01">
+    expires_after="2023-12-01">
   <owner>aidazolic@chromium.org</owner>
   <owner>chromeos-dlp@google.com</owner>
   <summary>
@@ -2639,43 +2639,6 @@
   <summary>Failure reason for OAuth token fetch for child user.</summary>
 </histogram>
 
-<histogram name="Enterprise.UserPolicyChromeOS.DelayInitialization" units="ms"
-    expires_after="M82">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Initialization delay due to loading the user policy cache.</summary>
-</histogram>
-
-<histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.ClientError"
-    enum="EnterpriseDeviceManagementStatus" expires_after="M82">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Policy client error during initial policy fetch.</summary>
-</histogram>
-
-<histogram
-    name="Enterprise.UserPolicyChromeOS.InitialFetch.DelayClientRegister"
-    units="ms" expires_after="M78">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Delay for registering the client with the policy server.</summary>
-</histogram>
-
-<histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.DelayOAuth2Token"
-    units="ms" expires_after="M77">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Delay for minting an OAuth2 acccess token.</summary>
-</histogram>
-
-<histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.DelayPolicyFetch"
-    units="ms" expires_after="M82">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Delay for fetching policy from the policy server.</summary>
-</histogram>
-
-<histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.DelayTotal"
-    units="ms" expires_after="M77">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Total delay for the initial policy fetch.</summary>
-</histogram>
-
 <histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.OAuth2Error"
     enum="GoogleServiceAuthError" expires_after="2023-12-01">
   <owner>igorcov@chromium.org</owner>
@@ -2689,12 +2652,6 @@
   </summary>
 </histogram>
 
-<histogram name="Enterprise.UserPolicyChromeOS.InitialFetch.OAuth2NetworkError"
-    enum="NetErrorCodes" expires_after="M81">
-  <owner>mnissler@chromium.org</owner>
-  <summary>Network error during OAuth2 access token fetch.</summary>
-</histogram>
-
 <histogram name="Enterprise.UserPolicyChromeOS.ReregistrationResult"
     enum="EnterpriseUserPolicyChromeOSReregistrationResult"
     expires_after="2023-12-01">
diff --git a/tools/metrics/histograms/metadata/login/histograms.xml b/tools/metrics/histograms/metadata/login/histograms.xml
index b433f7f2..a885f41 100644
--- a/tools/metrics/histograms/metadata/login/histograms.xml
+++ b/tools/metrics/histograms/metadata/login/histograms.xml
@@ -146,6 +146,17 @@
   </summary>
 </histogram>
 
+<histogram name="Login.DevicePolicyState" enum="DevicePoliciesState"
+    expires_after="2024-04-10">
+  <owner>igorcov@chromium.org</owner>
+  <owner>chromeos-commercial-remote-management@chromium.org</owner>
+  <summary>
+    Reports the device ownership, the state of the device policy and the state
+    of the owner key. Logged only for the devices that have ownership taken as
+    per install attributes. Is logged at every device policy load.
+  </summary>
+</histogram>
+
 <histogram name="Login.FailureReason" enum="LoginFailureReason"
     expires_after="2023-10-08">
   <owner>achuith@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 25cf150..73bf4b6 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -49,6 +49,7 @@
   <variant name=".Embedder_DefaultSearchEngine"/>
   <variant name=".Embedder_DirectURLInput"/>
   <variant name=".SpeculationRule"/>
+  <variant name=".SpeculationRuleFromIsolatedWorld"/>
 </variants>
 
 <histogram name="BackForwardCache.AllSites.EvictedAfterDocumentRestoredReason"
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index b12fb14..bfdfed7 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -8368,38 +8368,6 @@
   </summary>
 </histogram>
 
-<histogram name="OAuth2Login.PostMergeVerification"
-    enum="PostMergeVerificationOutcome" expires_after="2021-06-17">
-  <obsolete>
-    Deprecated on 2023-03-27 in M114. The code that generated this histogram was
-    removed.
-  </obsolete>
-  <owner>droger@chromium.org</owner>
-  <owner>msarda@chromium.org</owner>
-  <owner>chrome-signin-team@google.com</owner>
-  <summary>
-    Outcome of Chrome OS GAIA cookie post-merge session verification process. It
-    measures how often /MergeSession request collided with browser session
-    restore process resulting in partially authenticated primary GAIA session.
-  </summary>
-</histogram>
-
-<histogram name="OAuth2Login.PreMergeVerification"
-    enum="PostMergeVerificationOutcome" expires_after="2021-06-17">
-  <obsolete>
-    Deprecated on 2023-03-27 in M114. The code that generated this histogram was
-    removed.
-  </obsolete>
-  <owner>droger@chromium.org</owner>
-  <owner>msarda@chromium.org</owner>
-  <owner>chrome-signin-team@google.com</owner>
-  <summary>
-    Outcome of Chrome OS GAIA cookie pre-merge session verification process. It
-    measures how often we need to perform /MergeSession request to
-    re-authenticated exisitng user with GAIA.
-  </summary>
-</histogram>
-
 <histogram name="OAuth2Login.SessionRestore" enum="GaiaSessionRestoreOutcome"
     expires_after="2023-08-20">
   <owner>anastasiian@chromium.org</owner>
@@ -9835,17 +9803,6 @@
   </summary>
 </histogram>
 
-<histogram name="PushMessaging.BackgroundBudget" units="units"
-    expires_after="2018-08-30">
-  <owner>peter@chromium.org</owner>
-  <summary>
-    Whenever a Service Worker receives a push message, this records the budget
-    available to the service worker, which is an internal Chrome value for the
-    amount of background processing a service worker is allowed to do without
-    visibly alerting the user. Scale for the budget is 0 to 100.
-  </summary>
-</histogram>
-
 <histogram name="PushMessaging.CheckOriginForAbuseTime" units="ms"
     expires_after="2022-06-19">
   <owner>knollr@chromium.org</owner>
@@ -9937,24 +9894,6 @@
   </summary>
 </histogram>
 
-<histogram name="PushMessaging.SESForLowBudgetOrigin" units="units"
-    expires_after="2018-08-30">
-  <owner>peter@chromium.org</owner>
-  <summary>
-    When a Service Worker hits low budget when servicing a push message, this
-    records what the Site Engagement Service score is at that time.
-  </summary>
-</histogram>
-
-<histogram name="PushMessaging.SESForNoBudgetOrigin" units="units"
-    expires_after="2018-08-30">
-  <owner>peter@chromium.org</owner>
-  <summary>
-    When a Service Worker hits zero budget when servicing a push message, this
-    records what the Site Engagement Service score is at that time.
-  </summary>
-</histogram>
-
 <histogram name="PushMessaging.TimeToReadPersistedMessages" units="ms"
     expires_after="M98">
   <owner>peter@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index bc6d54a4..d6a525a 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -977,7 +977,7 @@
 </histogram>
 
 <histogram name="PasswordManager.BiometricAuthPwdFill.CanAuthenticate"
-    enum="BiometricsAvailability" expires_after="2023-04-23">
+    enum="BiometricsAvailability" expires_after="M118">
   <owner>ioanap@chromium.org</owner>
   <owner>fhorschig@chromium.org</owner>
   <summary>
@@ -2215,6 +2215,16 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.OpenedAsShortcut" enum="Boolean"
+    expires_after="2023-10-15">
+  <owner>vasilii@chromium.org</owner>
+  <owner>vsemeniuk@google.com</owner>
+  <summary>
+    Records whether Password Manager was opened as a standalone app or inside a
+    browser window. Recorded every time Password Manager is opened.
+  </summary>
+</histogram>
+
 <histogram name="PasswordManager.ParserDetectedOtpFieldWithRegex"
     enum="Boolean" expires_after="2023-07-01">
   <owner>kolos@chromium.org</owner>
@@ -3157,6 +3167,38 @@
   </summary>
 </histogram>
 
+<histogram name="PasswordManager.ReuseCheck.CheckedPasswords" units="passwords"
+    expires_after="2023-09-01">
+  <owner>vsemeniuk@google.com</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The number of passwords analyzed during the password reuse check. Note: this
+    is a number of unique password values. Recorded after reuse check is
+    finished.
+  </summary>
+</histogram>
+
+<histogram name="PasswordManager.ReuseCheck.ReusedPasswords" units="passwords"
+    expires_after="2023-09-01">
+  <owner>vsemeniuk@google.com</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The number of reused passwords found when the password reuse check
+    completed. Note: this is a number of unique password values. Recorded after
+    reuse check is finished.
+  </summary>
+</histogram>
+
+<histogram name="PasswordManager.ReuseCheck.Time" units="ms"
+    expires_after="2023-09-01">
+  <owner>vsemeniuk@google.com</owner>
+  <owner>vasilii@chromium.org</owner>
+  <summary>
+    The time it took to complete the password reuse check. Recorded after reuse
+    check is finished.
+  </summary>
+</histogram>
+
 <histogram name="PasswordManager.ReusedPasswordType" enum="ReusedPasswordType"
     expires_after="2023-10-15">
   <owner>vakh@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index 2471440..a7a9899c 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -1812,6 +1812,16 @@
   </summary>
 </histogram>
 
+<histogram name="SafeBrowsing.RT.GetCacheResultIsFromPastSession"
+    enum="Boolean" expires_after="2023-07-20">
+  <owner>thefrog@chromium.org</owner>
+  <owner>chrome-counter-abuse-alerts@google.com</owner>
+  <summary>
+    If the real-time URL cache lookup had a cache hit, logs whether the cache
+    entry was defined in a previous browser session rather than the current one.
+  </summary>
+</histogram>
+
 <histogram name="SafeBrowsing.RT.GetToken.Time" units="ms"
     expires_after="2023-10-15">
   <owner>xinghuilu@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
index 14df249..21461ae 100644
--- a/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/segmentation_platform/histograms.xml
@@ -36,6 +36,7 @@
   <variant name="ResumeHeavyUser"/>
   <variant name="SearchUser"/>
   <variant name="ShoppingUser"/>
+  <variant name="TabletProductivityUser"/>
 </variants>
 
 <variants name="Index">
@@ -97,7 +98,7 @@
   <variant name="SearchUserSegment"/>
   <variant name="Share"/>
   <variant name="ShoppingUser"/>
-  <variant name="TabletProductivityUserSegment"/>
+  <variant name="TabletProductivityUser"/>
   <variant name="Unknown"/>
   <variant name="Voice"/>
 </variants>
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index 20ea00f..3a062aa 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -512,9 +512,10 @@
   <owner>msarda@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
-    Records whether sync was turned on/consented on a profile at least once.
-    This histogram is recorded upon profile load, and only for the case where
-    the user is fully signed out.
+    Records whether sync was turned on/consented on a profile at least once
+    since the last time all profile data was wiped out (e.g. the user chose to
+    clear data upon signout). This histogram is recorded upon profile load, and
+    only for the case where the user is fully signed out.
   </summary>
 </histogram>
 
@@ -524,10 +525,11 @@
   <owner>msarda@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
-    Records whether sync was turned on/consented on a profile at least once.
-    This histogram is recorded upon profile load, and only if sync is off
-    (either the user is fully signed out, or the user is signed in without
-    turning sync on).
+    Records whether sync was turned on/consented on a profile at least once
+    since the last time all profile data was wiped out (e.g. the user chose to
+    clear data upon signout). This histogram is recorded upon profile load, and
+    only if sync is off (either the user is fully signed out, or the user is
+    signed in without turning sync on).
   </summary>
 </histogram>
 
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 62b3bb5..ac74cf6 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -6,7 +6,7 @@
         },
         "win": {
             "hash": "5df410606c35db860d68e0c3866484f23477a266",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4d46c527b1dd8a864a7f26e7549d22a0872d3dd1/trace_processor_shell.exe"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4f9f0278b3d294d668cae35171e5070be160536a/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "1d229abc94dea54ab4bb4327e78e18f942d08bf9",
@@ -14,15 +14,15 @@
         },
         "mac": {
             "hash": "d6a4d3c988c9b763491d45342c7a274ab7f045dc",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/4d46c527b1dd8a864a7f26e7549d22a0872d3dd1/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/4f9f0278b3d294d668cae35171e5070be160536a/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "7a4026b8718994145a52586fdec6e9447573345a",
             "full_remote_path": "perfetto-luci-artifacts/adbbb6c78e3a86c5e87b0338d9e42eb6b4ddbf4d/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "94074b17585e392d8e021b57516e6004f3e3022f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/4f9f0278b3d294d668cae35171e5070be160536a/trace_processor_shell"
+            "hash": "0f1eb1cbf6622d1184f5b526295855ff964d5d80",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/26b08771635aa44efa337d344336008dbcb65cdf/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
index b793bb2..40bfcdd 100644
--- a/ui/events/BUILD.gn
+++ b/ui/events/BUILD.gn
@@ -98,6 +98,7 @@
   sources = [
     "base_event_utils.cc",
     "base_event_utils.h",
+    "event_latency_metadata.h",
     "event_switches.cc",
     "event_switches.h",
     "events_base_export.h",
@@ -496,6 +497,7 @@
     "//ui/display",
     "//ui/gfx",
     "//ui/gfx/geometry",
+    "//ui/latency",
   ]
 
   defines = [ "GESTURE_DETECTION_IMPLEMENTATION" ]
diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc
index 9ad01f1..e7f0d58 100644
--- a/ui/events/blink/blink_event_util.cc
+++ b/ui/events/blink/blink_event_util.cc
@@ -335,6 +335,8 @@
   gesture.primary_unique_touch_event_id =
       details.primary_unique_touch_event_id();
   gesture.unique_touch_event_id = unique_touch_event_id;
+  gesture.GetModifiableEventLatencyMetadata() =
+      details.GetEventLatencyMetadata();
 
   switch (details.type()) {
     case ET_GESTURE_SHOW_PRESS:
diff --git a/ui/events/event_latency_metadata.h b/ui/events/event_latency_metadata.h
new file mode 100644
index 0000000..b61ccf8
--- /dev/null
+++ b/ui/events/event_latency_metadata.h
@@ -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.
+
+#ifndef UI_EVENTS_EVENT_LATENCY_METADATA_H_
+#define UI_EVENTS_EVENT_LATENCY_METADATA_H_
+
+#include "base/time/time.h"
+
+namespace ui {
+
+// The struct contains metadata about EventLatency events.
+// There should only be POD classes in this struct to keep the metadata to a
+// minimum.
+struct EventLatencyMetadata {
+  // Time when event arrived in the BrowserMain thread.
+  base::TimeTicks arrived_in_browser_main_timestamp;
+
+  // This field is used only by scroll events to understand when the related
+  // blocking touch move was dispatched to Renderer. If the related touch move
+  // wasn't blocking, this field is not set.
+  base::TimeTicks scrolls_blocking_touch_dispatched_to_renderer;
+
+  // Time when event was disppatched to the Renderer from the Browser.
+  base::TimeTicks dispatched_to_renderer;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_LATENCY_METADATA_H_
\ No newline at end of file
diff --git a/ui/events/gesture_detection/filtered_gesture_provider.cc b/ui/events/gesture_detection/filtered_gesture_provider.cc
index b832c1db..d497ee0 100644
--- a/ui/events/gesture_detection/filtered_gesture_provider.cc
+++ b/ui/events/gesture_detection/filtered_gesture_provider.cc
@@ -62,9 +62,11 @@
 void FilteredGestureProvider::OnTouchEventAck(
     uint32_t unique_event_id,
     bool event_consumed,
-    bool is_source_touch_event_set_blocking) {
+    bool is_source_touch_event_set_blocking,
+    const absl::optional<EventLatencyMetadata>& event_latency_metadata) {
   gesture_filter_.OnTouchEventAck(unique_event_id, event_consumed,
-                                  is_source_touch_event_set_blocking);
+                                  is_source_touch_event_set_blocking,
+                                  event_latency_metadata);
 }
 
 void FilteredGestureProvider::ResetGestureHandlingState() {
diff --git a/ui/events/gesture_detection/filtered_gesture_provider.h b/ui/events/gesture_detection/filtered_gesture_provider.h
index cbe9984..f02ff45 100644
--- a/ui/events/gesture_detection/filtered_gesture_provider.h
+++ b/ui/events/gesture_detection/filtered_gesture_provider.h
@@ -48,9 +48,15 @@
 
   // To be called upon asynchronous and synchronous ack of an event that was
   // forwarded after a successful call to |OnTouchEvent()|.
+  // |event_latency_metadata| is provided only if the touch event or
+  // corresponding touch event was blocked before sending to the Renderer. This
+  // definition of blocking is not related to the value of
+  // |is_source_touch_event_set_blocking|.
   void OnTouchEventAck(uint32_t unique_event_id,
                        bool event_consumed,
-                       bool is_source_touch_event_set_blocking);
+                       bool is_source_touch_event_set_blocking,
+                       const absl::optional<EventLatencyMetadata>&
+                           event_latency_metadata = absl::nullopt);
 
   void ResetGestureHandlingState();
 
diff --git a/ui/events/gesture_detection/gesture_event_data_packet.cc b/ui/events/gesture_detection/gesture_event_data_packet.cc
index dfd08725..bc6f6fe 100644
--- a/ui/events/gesture_detection/gesture_event_data_packet.cc
+++ b/ui/events/gesture_detection/gesture_event_data_packet.cc
@@ -122,4 +122,15 @@
   }
 }
 
+void GestureEventDataPacket::AddEventLatencyMetadataToGestures(
+    const EventLatencyMetadata& event_latency_metadata,
+    const base::RepeatingCallback<bool(const ui::GestureEventData&)>& filter) {
+  for (auto& gesture : gestures_) {
+    if (filter.Run(gesture)) {
+      gesture.details.GetModifiableEventLatencyMetadata() =
+          event_latency_metadata;
+    }
+  }
+}
+
 }  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_event_data_packet.h b/ui/events/gesture_detection/gesture_event_data_packet.h
index 285c837..8719711 100644
--- a/ui/events/gesture_detection/gesture_event_data_packet.h
+++ b/ui/events/gesture_detection/gesture_event_data_packet.h
@@ -7,8 +7,10 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <functional>
 
 #include "base/containers/stack_container.h"
+#include "base/functional/callback.h"
 #include "base/time/time.h"
 #include "ui/events/gesture_detection/gesture_detection_export.h"
 #include "ui/events/gesture_detection/gesture_event_data.h"
@@ -66,6 +68,10 @@
   AckState ack_state() { return ack_state_; }
   uint32_t unique_touch_event_id() const { return unique_touch_event_id_; }
 
+  void AddEventLatencyMetadataToGestures(
+      const EventLatencyMetadata& event_latency_metadata,
+      const base::RepeatingCallback<bool(const ui::GestureEventData&)>& filter);
+
  private:
   GestureEventDataPacket(base::TimeTicks timestamp,
                          GestureSource source,
diff --git a/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
index b4995671..c65b011 100644
--- a/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
+++ b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
@@ -134,4 +134,33 @@
   EXPECT_EQ(gfx::PointF(gesture.x, gesture.y), packet.touch_location());
 }
 
+TEST_F(GestureEventDataPacketTest, AddEventLatencyMetadataToGestures) {
+  GestureEventDataPacket packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::Action::DOWN));
+  packet.Push(CreateGesture(ET_GESTURE_TAP));
+  packet.Push(CreateGesture(ET_GESTURE_SCROLL_UPDATE));
+  packet.Push(CreateGesture(ET_GESTURE_PINCH_UPDATE));
+
+  EventLatencyMetadata event_latency_metadata;
+  event_latency_metadata.scrolls_blocking_touch_dispatched_to_renderer =
+      base::TimeTicks::Now();
+  packet.AddEventLatencyMetadataToGestures(
+      event_latency_metadata,
+      base::BindRepeating([](const ui::GestureEventData& data) {
+        return data.type() == ET_GESTURE_SCROLL_UPDATE;
+      }));
+
+  EXPECT_TRUE(packet.gesture(0)
+                  .details.GetEventLatencyMetadata()
+                  .scrolls_blocking_touch_dispatched_to_renderer.is_null());
+  EXPECT_EQ(
+      packet.gesture(1)
+          .details.GetEventLatencyMetadata()
+          .scrolls_blocking_touch_dispatched_to_renderer,
+      event_latency_metadata.scrolls_blocking_touch_dispatched_to_renderer);
+  EXPECT_TRUE(packet.gesture(2)
+                  .details.GetEventLatencyMetadata()
+                  .scrolls_blocking_touch_dispatched_to_renderer.is_null());
+}
+
 }  // namespace ui
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
index 219d06d3..4cb988e 100644
--- a/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
@@ -9,6 +9,7 @@
 #include "base/auto_reset.h"
 #include "base/check_op.h"
 #include "base/notreached.h"
+#include "base/trace_event/typed_macros.h"
 #include "ui/events/gesture_event_details.h"
 
 namespace ui {
@@ -134,6 +135,11 @@
          gesture_source == GestureEventDataPacket::TOUCH_START;
 }
 
+bool DoAddInputTimestampsToGesture(const GestureEventData& gesture_data) {
+  return gesture_data.type() == EventType::ET_GESTURE_SCROLL_UPDATE ||
+         gesture_data.type() == EventType::ET_GESTURE_SCROLL_BEGIN;
+}
+
 }  // namespace
 
 // TouchDispositionGestureFilter
@@ -201,7 +207,8 @@
 void TouchDispositionGestureFilter::OnTouchEventAck(
     uint32_t unique_touch_event_id,
     bool event_consumed,
-    bool is_source_touch_event_set_blocking) {
+    bool is_source_touch_event_set_blocking,
+    const absl::optional<EventLatencyMetadata>& event_latency_metadata) {
   // Spurious asynchronous acks should not trigger a crash.
   if (IsEmpty() || (Head().empty() && sequences_.size() == 1))
     return;
@@ -216,16 +223,17 @@
       Tail().back().gesture_source() != GestureEventDataPacket::TOUCH_TIMEOUT) {
     Tail().back().Ack(event_consumed, is_source_touch_event_set_blocking);
     if (sequences_.size() == 1 && Tail().size() == 1)
-      SendAckedEvents();
+      SendAckedEvents(event_latency_metadata);
   } else {
     DCHECK(!Head().empty());
     DCHECK_EQ(Head().front().unique_touch_event_id(), unique_touch_event_id);
     Head().front().Ack(event_consumed, is_source_touch_event_set_blocking);
-    SendAckedEvents();
+    SendAckedEvents(event_latency_metadata);
   }
 }
 
-void TouchDispositionGestureFilter::SendAckedEvents() {
+void TouchDispositionGestureFilter::SendAckedEvents(
+    const absl::optional<EventLatencyMetadata>& event_latency_metadata) {
   // Dispatch all packets corresponding to ack'ed touches, as well as
   // any pending timeout-based packets.
   bool touch_packet_for_current_ack_handled = false;
@@ -256,8 +264,20 @@
     // Aura, we could trigger a touch-cancel). As popping the sequence destroys
     // the packet, we copy the packet before popping it.
     touch_packet_for_current_ack_handled = true;
-    const GestureEventDataPacket packet = sequence.front();
+    GestureEventDataPacket packet = sequence.front();
     sequence.pop();
+
+    if (source == GestureEventDataPacket::TOUCH_MOVE &&
+        event_latency_metadata.has_value()) {
+      EventLatencyMetadata gesture_event_latency_metadata;
+      gesture_event_latency_metadata
+          .scrolls_blocking_touch_dispatched_to_renderer =
+          event_latency_metadata->dispatched_to_renderer;
+      packet.AddEventLatencyMetadataToGestures(
+          std::move(gesture_event_latency_metadata),
+          base::BindRepeating(DoAddInputTimestampsToGesture));
+    }
+
     FilterAndSendPacket(packet);
   }
   DCHECK(touch_packet_for_current_ack_handled);
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter.h b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
index 53bbd88..10f2bd0 100644
--- a/ui/events/gesture_detection/touch_disposition_gesture_filter.h
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
@@ -9,10 +9,12 @@
 
 #include "base/containers/queue.h"
 #include "base/memory/raw_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/events/gesture_detection/bitset_32.h"
 #include "ui/events/gesture_detection/gesture_detection_export.h"
 #include "ui/events/gesture_detection/gesture_event_data_packet.h"
 #include "ui/events/types/event_type.h"
+#include "ui/latency/latency_info.h"
 
 namespace ui {
 
@@ -53,9 +55,18 @@
   PacketResult OnGesturePacket(const GestureEventDataPacket& packet);
 
   // OnTouchEventAck must be called upon receipt of every touch event ack.
+  // |event_latency_metadata| is provided only if the touch event or
+  // corresponding touch event was blocked before sending to the Renderer. This
+  // definition of blocking is not related to the value of
+  // |is_source_touch_event_set_blocking| since
+  // |is_source_touch_event_set_blocking| refers to the behavior of blocking
+  // future inputs, not whether the current event was dispatched blocking to the
+  // renderer.
   void OnTouchEventAck(uint32_t unique_touch_event_id,
                        bool event_consumed,
-                       bool is_source_touch_event_set_blocking);
+                       bool is_source_touch_event_set_blocking,
+                       const absl::optional<EventLatencyMetadata>&
+                           event_latency_metadata = absl::nullopt);
 
   // Whether there are any active gesture sequences still queued in the filter.
   bool IsEmpty() const;
@@ -101,7 +112,8 @@
   void CancelFlingIfNecessary(const GestureEventDataPacket& packet);
   void EndScrollIfNecessary(const GestureEventDataPacket& packet);
   void PopGestureSequence();
-  void SendAckedEvents();
+  void SendAckedEvents(
+      const absl::optional<EventLatencyMetadata>& event_latency_metadata);
   GestureSequence& Head();
   GestureSequence& Tail();
 
diff --git a/ui/events/gesture_event_details.h b/ui/events/gesture_event_details.h
index ce44240..bdf8406 100644
--- a/ui/events/gesture_event_details.h
+++ b/ui/events/gesture_event_details.h
@@ -9,6 +9,7 @@
 
 #include "base/check_op.h"
 #include "ui/events/event_constants.h"
+#include "ui/events/event_latency_metadata.h"
 #include "ui/events/events_base_export.h"
 #include "ui/events/types/event_type.h"
 #include "ui/events/types/scroll_types.h"
@@ -185,6 +186,13 @@
     data_.scale = scale;
   }
 
+  const EventLatencyMetadata& GetEventLatencyMetadata() const {
+    return input_timestamps_;
+  }
+  EventLatencyMetadata& GetModifiableEventLatencyMetadata() {
+    return input_timestamps_;
+  }
+
   // Supports comparison over internal structures for testing.
   bool operator==(const GestureEventDetails& other) const {
     return type_ == other.type_ &&
@@ -259,6 +267,8 @@
   // Bounding box is an axis-aligned rectangle that contains all the
   // enclosing rectangles of the touch-points in the gesture.
   gfx::RectF bounding_box_;
+
+  EventLatencyMetadata input_timestamps_;
 };
 
 }  // namespace ui
diff --git a/ui/events/mojom/BUILD.gn b/ui/events/mojom/BUILD.gn
index a1f910e6..47a11bf 100644
--- a/ui/events/mojom/BUILD.gn
+++ b/ui/events/mojom/BUILD.gn
@@ -80,3 +80,24 @@
   blink_cpp_typemaps = shared_cpp_typemaps
   webui_module_path = "chrome://resources/mojo/ui/events/mojom"
 }
+
+mojom("event_latency_metadata_mojom") {
+  generate_java = true
+  sources = [ "event_latency_metadata.mojom" ]
+  public_deps = [ "//mojo/public/mojom/base" ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "ui.mojom.EventLatencyMetadata"
+          cpp = "::ui::EventLatencyMetadata"
+        },
+      ]
+
+      traits_headers = [ "event_latency_metadata_mojom_traits.h" ]
+      traits_sources = [ "event_latency_metadata_mojom_traits.cc" ]
+      traits_deps = [ "//ui/events:events_base" ]
+    },
+  ]
+}
diff --git a/ui/events/mojom/event_latency_metadata.mojom b/ui/events/mojom/event_latency_metadata.mojom
new file mode 100644
index 0000000..615bdad
--- /dev/null
+++ b/ui/events/mojom/event_latency_metadata.mojom
@@ -0,0 +1,23 @@
+// 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.
+
+module ui.mojom;
+
+import "mojo/public/mojom/base/time.mojom";
+
+// The struct contains metadata about EventLatency events.
+// There should only be POD classes in this struct to keep the metadata to a
+// minimum.
+struct EventLatencyMetadata {
+  // Time when event arrived in the BrowserMain thread.
+  mojo_base.mojom.TimeTicks arrived_in_browser_main_timestamp;
+
+  // This field is used only by scroll events to understand when the related
+  // blocking touch move was dispatched to Renderer. If the related touch move
+  // wasn't blocking, this field is not set.
+  mojo_base.mojom.TimeTicks scrolls_blocking_touch_dispatched_to_renderer;
+
+  // Time when event was disppatched to the Renderer from the Browser.
+  mojo_base.mojom.TimeTicks dispatched_to_renderer;
+};
diff --git a/ui/events/mojom/event_latency_metadata_mojom_traits.cc b/ui/events/mojom/event_latency_metadata_mojom_traits.cc
new file mode 100644
index 0000000..9d1fa06
--- /dev/null
+++ b/ui/events/mojom/event_latency_metadata_mojom_traits.cc
@@ -0,0 +1,27 @@
+// 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 "ui/events/mojom/event_latency_metadata_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<
+    ui::mojom::EventLatencyMetadataDataView,
+    ui::EventLatencyMetadata>::Read(ui::mojom::EventLatencyMetadataDataView in,
+                                    ui::EventLatencyMetadata* out) {
+  DCHECK(out != nullptr);
+
+  if (!in.ReadArrivedInBrowserMainTimestamp(
+          &out->arrived_in_browser_main_timestamp) ||
+      !in.ReadScrollsBlockingTouchDispatchedToRenderer(
+          &out->scrolls_blocking_touch_dispatched_to_renderer) ||
+      !in.ReadDispatchedToRenderer(&out->dispatched_to_renderer)) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace mojo
diff --git a/ui/events/mojom/event_latency_metadata_mojom_traits.h b/ui/events/mojom/event_latency_metadata_mojom_traits.h
new file mode 100644
index 0000000..04bcb4f
--- /dev/null
+++ b/ui/events/mojom/event_latency_metadata_mojom_traits.h
@@ -0,0 +1,38 @@
+// 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 UI_EVENTS_MOJOM_EVENT_LATENCY_METADATA_MOJOM_TRAITS_H_
+#define UI_EVENTS_MOJOM_EVENT_LATENCY_METADATA_MOJOM_TRAITS_H_
+
+#include "mojo/public/cpp/base/time_mojom_traits.h"
+#include "mojo/public/cpp/bindings/struct_traits.h"
+#include "ui/events/event_latency_metadata.h"
+#include "ui/events/mojom/event_latency_metadata.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<ui::mojom::EventLatencyMetadataDataView,
+                    ui::EventLatencyMetadata> {
+  static base::TimeTicks arrived_in_browser_main_timestamp(
+      const ui::EventLatencyMetadata& event_latency_metadata) {
+    return event_latency_metadata.arrived_in_browser_main_timestamp;
+  }
+
+  static base::TimeTicks scrolls_blocking_touch_dispatched_to_renderer(
+      const ui::EventLatencyMetadata& event_latency_metadata) {
+    return event_latency_metadata.scrolls_blocking_touch_dispatched_to_renderer;
+  }
+
+  static base::TimeTicks dispatched_to_renderer(
+      const ui::EventLatencyMetadata& event_latency_metadata) {
+    return event_latency_metadata.dispatched_to_renderer;
+  }
+  static bool Read(ui::mojom::EventLatencyMetadataDataView in,
+                   ui::EventLatencyMetadata* out);
+};
+
+}  // namespace mojo
+
+#endif  // UI_EVENTS_MOJOM_EVENT_LATENCY_METADATA_MOJOM_TRAITS_H_
\ No newline at end of file
diff --git a/ui/file_manager/file_manager/externs/ts/state.js b/ui/file_manager/file_manager/externs/ts/state.js
index f886dfd..380224b 100644
--- a/ui/file_manager/file_manager/externs/ts/state.js
+++ b/ui/file_manager/file_manager/externs/ts/state.js
@@ -381,6 +381,7 @@
  *   folderShortcuts: !Array<!FileKey>,
  *   androidApps: !Object<!string, !chrome.fileManagerPrivate.AndroidApp>,
  *   bulkPinning: (chrome.fileManagerPrivate.BulkPinProgress|undefined),
+ *   preferences: (chrome.fileManagerPrivate.Preferences|undefined),
  * }}
  */
 export let State;
diff --git a/ui/file_manager/file_manager/foreground/js/file_manager.js b/ui/file_manager/file_manager/foreground/js/file_manager.js
index b26a26f..905a7b9 100644
--- a/ui/file_manager/file_manager/foreground/js/file_manager.js
+++ b/ui/file_manager/file_manager/foreground/js/file_manager.js
@@ -29,6 +29,7 @@
 import {ForegroundWindow} from '../../externs/foreground_window.js';
 import {PropStatus} from '../../externs/ts/state.js';
 import {Store} from '../../externs/ts/store.js';
+import {updatePreferences} from '../../state/actions/preferences.js';
 import {updateSearch} from '../../state/actions/search.js';
 import {addUiEntry, removeUiEntry} from '../../state/actions/ui_entries.js';
 import {trashRootKey} from '../../state/reducers/volumes.js';
@@ -1678,6 +1679,8 @@
       return;
     }
 
+    this.store_.dispatch(updatePreferences(prefs));
+
     let redraw = false;
     if (this.driveEnabled_ !== prefs.driveEnabled) {
       this.driveEnabled_ = prefs.driveEnabled;
diff --git a/ui/file_manager/file_manager/state/actions.ts b/ui/file_manager/file_manager/state/actions.ts
index e54454a0..8635652 100644
--- a/ui/file_manager/file_manager/state/actions.ts
+++ b/ui/file_manager/file_manager/state/actions.ts
@@ -8,6 +8,7 @@
 import {ChangeDirectoryAction, ChangeFileTasksAction, ChangeSelectionAction, UpdateDirectoryContentAction} from './actions/current_directory.js';
 import {AddFolderShortcutAction, RefreshFolderShortcutAction, RemoveFolderShortcutAction} from './actions/folder_shortcuts.js';
 import {RefreshNavigationRootsAction, UpdateNavigationEntryAction} from './actions/navigation.js';
+import {UpdatePreferencesAction} from './actions/preferences.js';
 import {SearchAction} from './actions/search.js';
 import {AddUiEntryAction, RemoveUiEntryAction} from './actions/ui_entries.js';
 import {AddVolumeAction, RemoveVolumeAction} from './actions/volumes.js';
@@ -25,7 +26,8 @@
     AddUiEntryAction|RemoveUiEntryAction|UpdateDirectoryContentAction|
     UpdateMetadataAction|RefreshFolderShortcutAction|AddFolderShortcutAction|
     RemoveFolderShortcutAction|AddAndroidAppsAction|AddChildEntriesAction|
-    UpdateNavigationEntryAction|UpdateBulkPinProgressAction;
+    UpdateNavigationEntryAction|UpdateBulkPinProgressAction|
+    UpdatePreferencesAction;
 
 
 /** Enum to identify every Action in Files app. */
@@ -49,4 +51,5 @@
   UPDATE_METADATA = 'update-metadata',
   ADD_CHILD_ENTRIES = 'add-child-entries',
   UPDATE_BULK_PIN_PROGRESS = 'update-bulk-pin-progress',
+  UPDATE_PREFERENCES = 'update-preferences',
 }
diff --git a/ui/file_manager/file_manager/state/actions/preferences.ts b/ui/file_manager/file_manager/state/actions/preferences.ts
new file mode 100644
index 0000000..ee92344
--- /dev/null
+++ b/ui/file_manager/file_manager/state/actions/preferences.ts
@@ -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.
+
+import {BaseAction} from '../../lib/base_store.js';
+import {ActionType} from '../actions.js';
+
+/**
+ * Actions for Chrome Preferences.
+ *
+ * Chrome preferences store user data that is persisted to disk OR across
+ * profiles, this takes care of initially populating these values then keeping
+ * them updated on dynamic changes.
+ */
+
+/** Action to update the chrome preferences to the store. */
+export interface UpdatePreferencesAction extends BaseAction {
+  type: ActionType.UPDATE_PREFERENCES;
+  payload: chrome.fileManagerPrivate.PreferencesChange|
+      chrome.fileManagerPrivate.Preferences;
+}
+
+/** Action factory to update the user preferences to the store. */
+export function updatePreferences(payload: UpdatePreferencesAction['payload']):
+    UpdatePreferencesAction {
+  return {
+    type: ActionType.UPDATE_PREFERENCES,
+    payload,
+  };
+}
diff --git a/ui/file_manager/file_manager/state/reducers/bulk_pinning_unittest.ts b/ui/file_manager/file_manager/state/reducers/bulk_pinning_unittest.ts
index c7c36e80..e164d123 100644
--- a/ui/file_manager/file_manager/state/reducers/bulk_pinning_unittest.ts
+++ b/ui/file_manager/file_manager/state/reducers/bulk_pinning_unittest.ts
@@ -2,15 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assertDeepEquals} from 'chrome://webui-test/chromeos/chai_assert.js';
+
 import {updateBulkPinProgress} from '../actions/bulk_pinning.js';
-import {setupStore, waitDeepEquals} from '../for_tests.js';
+import {setupStore} from '../for_tests.js';
 
 /**
  * Tests that bulk pin progress updates the store and overwrites existing values
  * on each update.
  */
 export async function testUpdateBulkPinProgress(done: () => void) {
-  const bulkPinProgress: chrome.fileManagerPrivate.BulkPinProgress = {
+  const want: chrome.fileManagerPrivate.BulkPinProgress = {
     stage: chrome.fileManagerPrivate.BulkPinStage.STOPPED,
     freeSpaceBytes: 100,
     requiredSpaceBytes: 100,
@@ -21,17 +23,23 @@
 
   // Dispatch an action to update bulk pin progress.
   const store = setupStore();
-  store.dispatch(updateBulkPinProgress(bulkPinProgress));
+  store.dispatch(updateBulkPinProgress(want));
 
   // Expect the bulk pin progress to be updated.
-  waitDeepEquals(store, bulkPinProgress, (state) => state.bulkPinning);
+  const firstState = store.getState().bulkPinning;
+  assertDeepEquals(
+      want, firstState,
+      `1. ${JSON.stringify(want)} != ${JSON.stringify(firstState)}`);
 
   // Dispatch another action to change the stage to `SYNCING`.
-  bulkPinProgress.stage = chrome.fileManagerPrivate.BulkPinStage.SYNCING;
-  store.dispatch(updateBulkPinProgress(bulkPinProgress));
+  want.stage = chrome.fileManagerPrivate.BulkPinStage.SYNCING;
+  store.dispatch(updateBulkPinProgress(want));
 
   // Expect the bulk pin progress to equal the new state.
-  waitDeepEquals(store, bulkPinProgress, (state) => state.bulkPinning);
+  const secondState = store.getState().bulkPinning;
+  assertDeepEquals(
+      want, secondState,
+      `2. ${JSON.stringify(want)} != ${JSON.stringify(secondState)}`);
 
   done();
 }
diff --git a/ui/file_manager/file_manager/state/reducers/preferences.ts b/ui/file_manager/file_manager/state/reducers/preferences.ts
new file mode 100644
index 0000000..92395441
--- /dev/null
+++ b/ui/file_manager/file_manager/state/reducers/preferences.ts
@@ -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.
+
+/**
+ * @fileoverview Reducer for preferences.
+ *
+ * This file is checked via TS, so we suppress Closure checks.
+ * @suppress {checkTypes}
+ */
+
+import {State} from '../../externs/ts/state.js';
+import {UpdatePreferencesAction} from '../actions/preferences.js';
+
+/**
+ * Type alises to avoid writing the `chrome.fileManagerPrivate` prefix.
+ */
+export type Preferences = chrome.fileManagerPrivate.Preferences;
+export type PreferencesChange = chrome.fileManagerPrivate.PreferencesChange;
+
+/**
+ * A type guard to see if the payload supplied is a change of preferences or the
+ * entire preferences object. Useful in ensuring subsequent type checks are done
+ * on the correct type (instead of the union type).
+ */
+function isPreferencesChange(payload: Preferences|
+                             PreferencesChange): payload is PreferencesChange {
+  // The field `driveEnabled` is only on a `Preferences` object, so if this is
+  // undefined the payload is a `Preferences` object otherwise it's a
+  // `PreferencesChange` object.
+  if ((payload as Preferences).driveEnabled !== undefined) {
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Only update the existing preferences with their new values if they are
+ * defined. In the event of spreading the change event over the existing
+ * preferences, undefined values should not overwrite their existing values.
+ */
+function updateIfDefined(
+    updatedPreferences: Preferences, newPreferences: PreferencesChange,
+    key: keyof PreferencesChange): boolean {
+  if (!(key in newPreferences) || newPreferences[key] === undefined) {
+    return false;
+  }
+  if (updatedPreferences[key] === newPreferences[key]) {
+    return false;
+  }
+  // We're updating the `Preferences` original here and it doesn't type union
+  // well with `PreferencesChange`. Given we've done all the type validation
+  // above, cast them both to the `Preferences` type to ensure subsequent
+  // updates can work.
+  (updatedPreferences[key] as Preferences[keyof Preferences]) =
+      newPreferences[key] as Preferences[keyof Preferences];
+  return true;
+}
+
+export function updatePreferences(
+    currentState: State, action: UpdatePreferencesAction): State {
+  const preferences = action.payload;
+
+  // This action takes two potential payloads:
+  //  - chrome.fileManagerPrivate.Preferences
+  //  - chrome.fileManagerPrivate.PreferencesChange
+  // Both of these have different type requirements. If we receive a
+  // `Preferences` update, just store the data directly in the store. If we
+  // receive a `PreferencesChange` the individual fields need to be checked to
+  // ensure they are different to what we have in the store AND they won't
+  // remove the existing data (i.e. they are not null or undefined).
+  if (!isPreferencesChange(preferences)) {
+    return {
+      ...currentState,
+      preferences,
+    };
+  }
+
+  const updatedPreferences = {...currentState.preferences!};
+  const keysToCheck: Array<keyof PreferencesChange> = [
+    'cellularDisabled',
+    'arcEnabled',
+    'arcRemovableMediaAccessEnabled',
+    'folderShortcuts',
+    'driveFsBulkPinningEnabled',
+  ];
+  let updated = false;
+  for (const key of keysToCheck) {
+    updated = updateIfDefined(updatedPreferences, preferences, key) || updated;
+  }
+
+  // If no keys have been updated in the preference change, then send back the
+  // original state as nothing has changed.
+  if (!updated) {
+    return currentState;
+  }
+
+  return {
+    ...currentState,
+    preferences: updatedPreferences,
+  };
+}
diff --git a/ui/file_manager/file_manager/state/reducers/preferences_unittest.ts b/ui/file_manager/file_manager/state/reducers/preferences_unittest.ts
new file mode 100644
index 0000000..8013a6a
--- /dev/null
+++ b/ui/file_manager/file_manager/state/reducers/preferences_unittest.ts
@@ -0,0 +1,96 @@
+// 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 {assertDeepEquals} from 'chrome://webui-test/chromeos/chai_assert.js';
+
+import {updatePreferences} from '../actions/preferences.js';
+import {setupStore} from '../for_tests.js';
+
+import {Preferences} from './preferences.js';
+
+/**
+ * Defines an initial state for user preferences that is used in all the tests.
+ */
+const INITIAL_PREFERENCES: Preferences = {
+  driveEnabled: false,
+  cellularDisabled: false,
+  searchSuggestEnabled: false,
+  use24hourClock: false,
+  timezone: 'GMT+10',
+  arcEnabled: false,
+  arcRemovableMediaAccessEnabled: false,
+  folderShortcuts: [],
+  trashEnabled: false,
+  officeFileMovedOneDrive: 0,
+  officeFileMovedGoogleDrive: 0,
+  driveFsBulkPinningEnabled: false,
+};
+
+/**
+ * Tests that bulk pin progress updates the store and overwrites existing values
+ * on each update.
+ */
+export async function testUpdatePreferences(done: () => void) {
+  // Dispatch an action to update user preferences.
+  const store = setupStore();
+  store.dispatch(updatePreferences(INITIAL_PREFERENCES));
+
+  // Expect the preferences in the store to be updated.
+  const firstState = store.getState().preferences;
+  const want = INITIAL_PREFERENCES;
+  assertDeepEquals(
+      want, firstState,
+      `${JSON.stringify(want)} != ${JSON.stringify(firstState)}`);
+
+  done();
+}
+
+export async function testPreferencesWithNoKeysUpdates(done: () => void) {
+  // Dispatch an action to update bulk pin progress.
+  const store = setupStore();
+  store.dispatch(updatePreferences(INITIAL_PREFERENCES));
+
+  /**
+   * Test an individual preference update type.
+   * NOTE: Closure types defined on the fileManagerPrivate.PreferencesChange
+   * require that if 1 preference is being updated all others must have a key of
+   * undefined, however, this doesn't mimic the real world scenario of the keys
+   * not existing. Use `any` to ensure this can be passed through.
+   */
+  const testPreferenceUpdate =
+      (preferenceUpdate: any, initialPreferences: Preferences): Preferences => {
+        store.dispatch(updatePreferences(preferenceUpdate));
+
+        const want = {
+          ...initialPreferences,
+          ...preferenceUpdate,
+        };
+
+        // Expect the preferences in the store to be updated.
+        const state = store.getState().preferences;
+        assertDeepEquals(
+            want, state, `${JSON.stringify(want)} != ${JSON.stringify(state)}`);
+
+        return want;
+      };
+
+  // Verify all the `boolean` type preferences update appropriately, they are
+  // all initially `false` and this updates them all to `true` one by one.
+  let preferences = INITIAL_PREFERENCES;
+  const booleanPreferences = [
+    'cellularDisabled',
+    'arcEnabled',
+    'arcRemovableMediaAccessEnabled',
+    'driveFsBulkPinningEnabled',
+  ];
+  for (const pref of booleanPreferences) {
+    preferences = testPreferenceUpdate({[pref]: true}, preferences);
+  }
+
+  // Folder shortcuts are an array, so test them separately.
+  const folderShortcutsUpdate = {folderShortcuts: ['some/shortcut']};
+  testPreferenceUpdate(folderShortcutsUpdate, preferences);
+
+  done();
+}
diff --git a/ui/file_manager/file_manager/state/reducers/root.ts b/ui/file_manager/file_manager/state/reducers/root.ts
index a8f9c04d..175ce06 100644
--- a/ui/file_manager/file_manager/state/reducers/root.ts
+++ b/ui/file_manager/file_manager/state/reducers/root.ts
@@ -11,6 +11,7 @@
 import {changeDirectory, updateDirectoryContent, updateFileTasks, updateSelection} from './current_directory.js';
 import {addFolderShortcut, refreshFolderShortcut, removeFolderShortcut} from './folder_shortcuts.js';
 import {refreshNavigationRoots, updateNavigationEntry} from './navigation.js';
+import {updatePreferences} from './preferences.js';
 import {search} from './search.js';
 import {addUiEntry, removeUiEntry} from './ui_entries.js';
 import {addVolume, removeVolume} from './volumes.js';
@@ -67,6 +68,8 @@
       return addChildEntries(currentState, action);
     case ActionType.UPDATE_BULK_PIN_PROGRESS:
       return updateBulkPinning(currentState, action);
+    case ActionType.UPDATE_PREFERENCES:
+      return updatePreferences(currentState, action);
     default:
       console.error(`invalid action type: ${(action as any)?.type} action: ${
           JSON.stringify(action)}`);
diff --git a/ui/file_manager/file_manager/state/store.ts b/ui/file_manager/file_manager/state/store.ts
index e282f81..872a6fd 100644
--- a/ui/file_manager/file_manager/state/store.ts
+++ b/ui/file_manager/file_manager/state/store.ts
@@ -59,6 +59,7 @@
     folderShortcuts: [],
     androidApps: [],
     bulkPinning: undefined,
+    preferences: undefined,
   };
 }
 
diff --git a/ui/file_manager/file_names.gni b/ui/file_manager/file_names.gni
index d709114d..54e694d 100644
--- a/ui/file_manager/file_names.gni
+++ b/ui/file_manager/file_names.gni
@@ -260,6 +260,7 @@
   "file_manager/state/actions/current_directory.ts",
   "file_manager/state/actions/folder_shortcuts.ts",
   "file_manager/state/actions/navigation.ts",
+  "file_manager/state/actions/preferences.ts",
   "file_manager/state/actions/search.ts",
   "file_manager/state/actions/ui_entries.ts",
   "file_manager/state/actions/volumes.ts",
@@ -276,6 +277,7 @@
   "file_manager/state/reducers/current_directory.ts",
   "file_manager/state/reducers/folder_shortcuts.ts",
   "file_manager/state/reducers/navigation.ts",
+  "file_manager/state/reducers/preferences.ts",
   "file_manager/state/reducers/search.ts",
   "file_manager/state/reducers/ui_entries.ts",
   "file_manager/state/reducers/volumes.ts",
@@ -351,6 +353,7 @@
   "file_manager/state/reducers/current_directory_unittest.ts",
   "file_manager/state/reducers/folder_shortcuts_unittest.ts",
   "file_manager/state/reducers/navigation_unittest.ts",
+  "file_manager/state/reducers/preferences_unittest.ts",
   "file_manager/state/reducers/search_unittest.ts",
   "file_manager/state/reducers/ui_entries_unittest.ts",
   "file_manager/state/reducers/volumes_unittest.ts",
diff --git a/ui/latency/latency_info.cc b/ui/latency/latency_info.cc
index dd76d2a..ef364fc 100644
--- a/ui/latency/latency_info.cc
+++ b/ui/latency/latency_info.cc
@@ -148,11 +148,10 @@
   AddLatencyNumberWithTimestampImpl(component, base::TimeTicks::Now(), nullptr);
 }
 
-void LatencyInfo::AddLatencyNumberWithTraceName(
-    LatencyComponentType component,
-    const char* trace_name_str) {
-  AddLatencyNumberWithTimestampImpl(component, base::TimeTicks::Now(),
-                                    trace_name_str);
+void LatencyInfo::AddLatencyNumberWithTraceName(LatencyComponentType component,
+                                                const char* trace_name_str,
+                                                base::TimeTicks now) {
+  AddLatencyNumberWithTimestampImpl(component, now, trace_name_str);
 }
 
 void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
diff --git a/ui/latency/latency_info.h b/ui/latency/latency_info.h
index 64dfa11..3456e68 100644
--- a/ui/latency/latency_info.h
+++ b/ui/latency/latency_info.h
@@ -130,7 +130,8 @@
   // the trace event's name.
   // This function should only be called when adding a BEGIN component.
   void AddLatencyNumberWithTraceName(LatencyComponentType component,
-                                     const char* trace_name_str);
+                                     const char* trace_name_str,
+                                     base::TimeTicks now);
 
   // Modifies the current sequence number and adds a certain number of events
   // for a specific component.