diff --git a/DEPS b/DEPS
index 0dc23b17..380ffd7 100644
--- a/DEPS
+++ b/DEPS
@@ -239,15 +239,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': 'd26057a2c0e1e35a2f3a498769a27d8f8b6b1e62',
+  'skia_revision': '16d3cc04cc85928e3492bc2df7e0a88b8422b73c',
   # 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': '8a05d7add33e40e73aac9fc43b4d9cce04adeaca',
+  'v8_revision': 'b5bf62dafe52d10d65bfd2e378d658b5b3b04a5e',
   # 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': '42bd4fc29aea6c0c1722d3604b0eadf7fb1e9aca',
+  'angle_revision': 'fefd7ae66ad94d8fcb16ef9f5f7304c1b4b9fbf8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -314,7 +314,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': 'a1121f966ce5590b46706befdf60e3350a5cf2d1',
+  'devtools_frontend_revision': '5fec567a800a98035a5c7aa02eebf93d3690e484',
   # 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.
@@ -716,7 +716,7 @@
       'packages': [
         {
           'package': 'chromium/rts/model/linux-amd64',
-          'version': 'vdD8aHXAya55_zVdy0rcmEfQix9y1GLancfERg2yKc8C',
+          'version': 'h4OErwvGMzWmTzk59jLdAUp9o00HkQ6vKZlwtDCstocC',
         },
       ],
       'dep_type': 'cipd',
@@ -799,7 +799,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'juajBfBlqvumlCoLLOxvOPlX6cPvfNhDi8XyKP4yjTEC',
+          'version': 'Cd5XvW44KjLwajSIYoTsODMnpNKBR--BNLPfpIDKGeAC',
       },
     ],
     'condition': 'checkout_android',
@@ -1033,7 +1033,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '738cc02fc3beb133d085f871e2534416baf5afc7',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '188fb4eba05af428a48c7ce173a3026215241850',
       'condition': 'checkout_linux',
   },
 
@@ -1421,7 +1421,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '1357bd132733ccc23a295739e4bc432d7a53d120',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f9abf9948a180a56a3595ec54ff9f2f5c2c9947c',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1642,7 +1642,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '216e2ff413d3ed5e7695626bb55ae41575818352',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '699d1a242ecd3ca819293cfa96f5dcf8f5e1b91a',
+    Var('webrtc_git') + '/src.git' + '@' + '63b97de330fe3d4775b9b4df8ad15c7593d58fc0',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1700,7 +1700,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@d49909553bd8838195664ca9df5b36229c2582da',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@3f761f4e33519a151e99f110643c052677d2d7a2',
     'condition': 'checkout_src_internal',
   },
 
@@ -1719,7 +1719,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'uwAu1UD0oDj17zqx0Af0wZnVecasHn1pBC4V6A3JBRwC',
+        'version': 'RcDM8Mq_LrCGiuxfQto-gkDVFbqCRy6FE8lzPeHsNEwC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1730,7 +1730,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'Hm4ipUPiXd-gzWDEbyraNGipQikDIbxi1Xc70s6BwWQC',
+        'version': 'OEszP2fR0fctn9Qr3fnIElqnNLPxHyhTUfWfaiiLVckC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/app_list/views/apps_container_view.cc b/ash/app_list/views/apps_container_view.cc
index 52e11026..8a65e8f 100644
--- a/ash/app_list/views/apps_container_view.cc
+++ b/ash/app_list/views/apps_container_view.cc
@@ -54,6 +54,7 @@
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/flex_layout.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/view_utils.h"
 
 namespace ash {
@@ -131,6 +132,9 @@
 // The number of columns available for the ContinueSectionView.
 constexpr int kContinueColumnCount = 4;
 
+// The vertical spacing between recent apps and continue section view.
+constexpr int kRecentAppsTopMargin = 16;
+
 // The vertical spacing above and below the separator.
 constexpr int kSeparatorVerticalInset = 16;
 
@@ -261,6 +265,7 @@
   ContinueContainer(AppsContainerView* apps_container,
                     AppListViewDelegate* view_delegate) {
     SetPaintToLayer(ui::LAYER_NOT_DRAWN);
+
     SetLayoutManager(std::make_unique<views::FlexLayout>())
         ->SetOrientation(views::LayoutOrientation::kVertical);
 
@@ -278,16 +283,16 @@
     separator_ = AddChildView(std::make_unique<views::Separator>());
     separator_->SetColor(ColorProvider::Get()->GetContentLayerColor(
         ColorProvider::ContentLayerType::kSeparatorColor));
-    separator_->SetBorder(
-        views::CreateEmptyBorder(gfx::Insets(kSeparatorVerticalInset, 0)));
     separator_->SetPreferredSize(
-        gfx::Size(kSeparatorWidth,
-                  kSeparatorVerticalInset * 2 + views::Separator::kThickness));
+        gfx::Size(kSeparatorWidth, views::Separator::kThickness));
+    separator_->SetProperty(views::kMarginsKey,
+                            gfx::Insets(kSeparatorVerticalInset, 0));
     separator_->SetPaintToLayer();
     separator_->layer()->SetFillsBoundsOpaquely(false);
     separator_->SetProperty(views::kCrossAxisAlignmentKey,
                             views::LayoutAlignment::kCenter);
 
+    UpdateRecentAppsMargins();
     UpdateSeparatorVisibility();
   }
 
@@ -295,6 +300,9 @@
   void ChildVisibilityChanged(views::View* child) override {
     if (child == recent_apps_ || child == continue_section_)
       UpdateSeparatorVisibility();
+
+    if (child == continue_section_)
+      UpdateRecentAppsMargins();
   }
 
   void OnThemeChanged() override {
@@ -308,6 +316,16 @@
   views::View* separator() { return separator_; }
 
  private:
+  void UpdateRecentAppsMargins() {
+    if (!recent_apps_ || !continue_section_)
+      return;
+    // Remove recent apps top margin if continue section is hidden.
+    recent_apps_->SetProperty(
+        views::kMarginsKey,
+        gfx::Insets(continue_section_->GetVisible() ? kRecentAppsTopMargin : 0,
+                    0, 0, 0));
+  }
+
   void UpdateSeparatorVisibility() {
     if (!separator_ || !recent_apps_ || !continue_section_)
       return;
@@ -809,22 +827,24 @@
       GetContentsBounds(),
       contents_view_->GetSearchBoxSize(AppListState::kStateApps));
   gfx::Rect grid_rect = rect;
-  grid_rect.Inset(margins.left(), kGridFadeoutZoneHeight - grid_insets.top(),
-                  margins.right(), margins.bottom());
+  grid_rect.Inset(margins.left(), kGridFadeoutZoneHeight, margins.right(),
+                  margins.bottom());
   // The grid rect insets are added to calculated margins. Given that the
   // grid bounds rect should include insets, they have to be removed from
   // added margins.
-  grid_rect.Inset(-grid_insets.left(), 0, -grid_insets.right(),
-                  -grid_insets.bottom());
+  grid_rect.Inset(-grid_insets);
   scrollable_container_->SetBoundsRect(grid_rect);
+  bool first_page_offset_changed = false;
   if (features::IsProductivityLauncherEnabled()) {
+    const int continue_container_height =
+        continue_container_->GetPreferredSize().height();
     continue_container_->SetBoundsRect(
-        gfx::Rect(0, 0, grid_rect.width(),
-                  continue_container_->GetPreferredSize().height()));
+        gfx::Rect(0, 0, grid_rect.width(), continue_container_height));
     // Setting this offset prevents the app items in the grid from overlapping
     // with the continue section.
-    apps_grid_view_->set_first_page_offset(
-        continue_container_->bounds().height());
+    first_page_offset_changed =
+        continue_container_height != apps_grid_view_->first_page_offset();
+    apps_grid_view_->set_first_page_offset(continue_container_height);
   }
 
   // Make sure that UpdateTopLevelGridDimensions() happens after setting the
@@ -832,8 +852,15 @@
   // shown in the grid.
   UpdateTopLevelGridDimensions();
 
-  apps_grid_view_->SetBoundsRect(
-      gfx::Rect(0, 0, grid_rect.width(), grid_rect.height()));
+  const gfx::Rect apps_grid_bounds(grid_rect.size());
+  if (apps_grid_view_->bounds() != apps_grid_bounds) {
+    apps_grid_view_->SetBoundsRect(apps_grid_bounds);
+  } else if (first_page_offset_changed) {
+    // Apps grid layout depends on the continue container bounds, so explicitly
+    // call layout to ensure apps grid view gets laid out even if its bounds do
+    // not change.
+    apps_grid_view_->Layout();
+  }
 
   // Record the distance of y position between suggestion chip container
   // and apps grid view to avoid duplicate calculation of apps grid view's
@@ -1036,11 +1063,12 @@
   const int suggestion_chip_container_size =
       features::IsProductivityLauncherEnabled()
           ? 0
-          : kSuggestionChipContainerHeight;
+          : kSuggestionChipContainerHeight + kSuggestionChipContainerTopMargin;
+
   // NOTE: Use the fadeout zone height as min top margin to match the apps grid
   // view's bottom margin.
   return search_box_size.height() + kGridFadeoutZoneHeight +
-         kSuggestionChipContainerTopMargin + suggestion_chip_container_size;
+         suggestion_chip_container_size;
 }
 
 int AppsContainerView::GetIdealHorizontalMargin() const {
diff --git a/ash/app_list/views/continue_section_view.cc b/ash/app_list/views/continue_section_view.cc
index a4dcf4f..478e65a 100644
--- a/ash/app_list/views/continue_section_view.cc
+++ b/ash/app_list/views/continue_section_view.cc
@@ -31,11 +31,6 @@
 namespace ash {
 namespace {
 
-// Continue File Section view paddings. This view encloses the header and the
-// suggested tasks container.
-constexpr int kSectionVerticalPadding = 16;
-constexpr int kSectionHorizontalPadding = 20;
-
 // Header paddings in dips.
 constexpr int kHeaderVerticalSpacing = 4;
 constexpr int kHeaderHorizontalPadding = 12;
@@ -61,8 +56,7 @@
   AppListModelProvider::Get()->AddObserver(this);
 
   auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical,
-      gfx::Insets(kSectionVerticalPadding, kSectionHorizontalPadding),
+      views::BoxLayout::Orientation::kVertical, gfx::Insets(),
       kHeaderVerticalSpacing));
   layout->set_main_axis_alignment(
       tablet_mode ? views::BoxLayout::MainAxisAlignment::kCenter
diff --git a/ash/app_list/views/continue_section_view_unittest.cc b/ash/app_list/views/continue_section_view_unittest.cc
index 0d4381f..ff50472 100644
--- a/ash/app_list/views/continue_section_view_unittest.cc
+++ b/ash/app_list/views/continue_section_view_unittest.cc
@@ -652,7 +652,7 @@
 
   // Set the display width so only 2 continue section tasks fit into available
   // space.
-  UpdateDisplay("800x600");
+  UpdateDisplay("600x800");
 
   EnsureLauncherShown();
   VerifyResultViewsUpdated();
diff --git a/ash/app_list/views/paged_apps_grid_view.cc b/ash/app_list/views/paged_apps_grid_view.cc
index c8ddb6e..5c59683 100644
--- a/ash/app_list/views/paged_apps_grid_view.cc
+++ b/ash/app_list/views/paged_apps_grid_view.cc
@@ -589,7 +589,8 @@
   if (IsInFolder())
     return;
 
-  SetBorder(views::CreateEmptyBorder(gfx::Insets(GetFadeoutMaskHeight(), 0)));
+  if (!features::IsProductivityLauncherEnabled())
+    SetBorder(views::CreateEmptyBorder(gfx::Insets(GetFadeoutMaskHeight(), 0)));
 }
 
 void PagedAppsGridView::MaybeStartCardifiedView() {
@@ -1221,8 +1222,9 @@
                                         ? first_page_vertical_tile_padding_
                                         : vertical_tile_padding_;
   const gfx::Size background_card_size =
-      grid_size +
-      gfx::Size(2 * horizontal_tile_padding_, 2 * vertical_tile_padding);
+      grid_size + gfx::Size(2 * horizontal_tile_padding_,
+                            2 * std::max(kMinVerticalPaddingBetweenTiles,
+                                         vertical_tile_padding));
 
   // Add a padding on the sides to make space for pagination preview, but make
   // sure the padding doesn't exceed the tile padding (otherwise the background
@@ -1233,13 +1235,13 @@
       (GetContentsBounds().width() - background_card_size.width()) / 2 +
       extra_padding_for_cardified_state;
 
-  const int y_offset = (new_page_index == 0 ? first_page_offset_ : 0);
+  int y_offset = std::max(new_page_index == 0 ? first_page_offset_ : 0,
+                          GetFadeoutMaskHeight());
   // The vertical padding should account for the fadeout mask.
-  const int vertical_padding = y_offset +
-                               (GetContentsBounds().height() - y_offset -
-                                background_card_size.height()) /
-                                   2 +
-                               GetFadeoutMaskHeight();
+  const int vertical_padding =
+      y_offset + (GetContentsBounds().height() - y_offset -
+                  background_card_size.height()) /
+                     2;
   const int padding_between_pages = GetPaddingBetweenPages();
   // The space that each page occupies in the items container. This is the size
   // of the grid without outer padding plus the padding between pages.
@@ -1314,6 +1316,10 @@
   if (cardified_state_) {
     content_size = gfx::ScaleToRoundedSize(content_size, kCardifiedScale) -
                    gfx::Size(2 * kCardifiedHorizontalPadding, 0);
+    content_size.set_width(
+        std::max(content_size.width(), cols() * tile_size.width()));
+    content_size.set_height(
+        std::max(content_size.height(), max_rows_ * tile_size.height()));
   }
 
   const auto calculate_tile_padding = [](int content_size, int num_tiles,
diff --git a/ash/app_list/views/paged_apps_grid_view.h b/ash/app_list/views/paged_apps_grid_view.h
index 8796b500..bd9c030 100644
--- a/ash/app_list/views/paged_apps_grid_view.h
+++ b/ash/app_list/views/paged_apps_grid_view.h
@@ -150,6 +150,7 @@
   // Gets the PaginationModel used for the grid view.
   PaginationModel* pagination_model() { return &pagination_model_; }
 
+  int first_page_offset() const { return first_page_offset_; }
   void set_first_page_offset(int offset) { first_page_offset_ = offset; }
 
   // Calculates the maximum number of rows on the first page. Relies on tile
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
index e45a0e5..589f4e8 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.cc
@@ -8,6 +8,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/sparse_histogram.h"
+
 namespace {
 
 const char kEngagementFlowInitialMetric[] =
@@ -19,6 +20,9 @@
     "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.InitialPairingProtocol";
 const char kTotalUxPairTimeSubsequentMetric[] =
     "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.SubsequentPairingProtocol";
+const char kRetroactiveEngagementFlowMetric[] =
+    "Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps";
+const char kPairingMethodMetric[] = "Bluetooth.ChromeOS.FastPair.PairingMethod";
 
 }  // namespace
 
@@ -56,5 +60,23 @@
   }
 }
 
+void AttemptRecordingFastPairRetroactiveEngagementFlow(
+    const Device& device,
+    FastPairRetroactiveEngagementFlowEvent event) {
+  switch (device.protocol) {
+    case Protocol::kFastPairInitial:
+    case Protocol::kFastPairSubsequent:
+      break;
+    case Protocol::kFastPairRetroactive:
+      base::UmaHistogramSparse(kRetroactiveEngagementFlowMetric,
+                               static_cast<int>(event));
+      break;
+  }
+}
+
+void RecordPairingMethod(PairingMethod method) {
+  base::UmaHistogramEnumeration(kPairingMethodMetric, method);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
index b0ead28a..073904ad 100644
--- a/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
+++ b/ash/quick_pair/common/fast_pair/fast_pair_metrics.h
@@ -27,6 +27,33 @@
   kErrorUiSettingsPressed = 1212,
 };
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. The numbers here correspond to the
+// ordering of the flow. This enum should be kept in sync with the
+// FastPairRetroactiveEngagementFlowEvent enum in
+// src/tools/metrics/histograms/enums.xml.
+enum COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+    FastPairRetroactiveEngagementFlowEvent {
+      kAssociateAccountUiShown = 1,
+      kAssociateAccountUiDismissedByUser = 11,
+      kAssociateAccountUiDismissed = 12,
+      kAssociateAccountLearnMorePressed = 13,
+      kAssociateAccountSavePressed = 14,
+      kAssociateAccountSavePressedAfterLearnMorePressed = 131,
+      kAssociateAccountDismissedByUserAfterLearnMorePressed = 132,
+      kAssociateAccountDismissedAfterLearnMorePressed = 133,
+    };
+
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. This enum should be kept in sync
+// with the FastPairPairingMethod enum in
+// src/tools/metrics/histograms/enums.xml.
+enum class COMPONENT_EXPORT(QUICK_PAIR_COMMON) PairingMethod {
+  kFastPair = 0,
+  kSystemPairingUi = 1,
+  kMaxValue = kSystemPairingUi,
+};
+
 COMPONENT_EXPORT(QUICK_PAIR_COMMON)
 void AttemptRecordingFastPairEngagementFlow(const Device& device,
                                             FastPairEngagementFlowEvent event);
@@ -35,6 +62,14 @@
 void AttemptRecordingTotalUxPairTime(const Device& device,
                                      base::TimeDelta total_pair_time);
 
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+void AttemptRecordingFastPairRetroactiveEngagementFlow(
+    const Device& device,
+    FastPairRetroactiveEngagementFlowEvent event);
+
+COMPONENT_EXPORT(QUICK_PAIR_COMMON)
+void RecordPairingMethod(PairingMethod method);
+
 }  // namespace quick_pair
 }  // namespace ash
 
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.cc b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
index 519f313..f6cf807 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
@@ -84,7 +84,8 @@
       fast_pair_bluetooth_config_delegate_(
           std::make_unique<FastPairBluetoothConfigDelegate>()) {
   metrics_logger_ = std::make_unique<QuickPairMetricsLogger>(
-      scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get());
+      scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get(),
+      retroactive_pairing_detector_.get());
   battery_update_message_handler_ =
       std::make_unique<BatteryUpdateMessageHandler>(
           message_stream_lookup_.get());
diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
index 736742e..bc8d9b3 100644
--- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.cc
@@ -7,29 +7,77 @@
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/fast_pair/fast_pair_feature_usage_metrics_logger.h"
 #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
+#include "base/containers/contains.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
 
 namespace ash {
 namespace quick_pair {
 
-QuickPairMetricsLogger::QuickPairMetricsLogger(ScannerBroker* scanner_broker,
-                                               PairerBroker* pairer_broker,
-                                               UIBroker* ui_broker)
+QuickPairMetricsLogger::QuickPairMetricsLogger(
+    ScannerBroker* scanner_broker,
+    PairerBroker* pairer_broker,
+    UIBroker* ui_broker,
+    RetroactivePairingDetector* retroactive_pairing_detector)
     : feature_usage_metrics_logger_(
           std::make_unique<FastPairFeatureUsageMetricsLogger>()) {
+  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
+      &QuickPairMetricsLogger::OnGetAdapter, weak_ptr_factory_.GetWeakPtr()));
+
   scanner_broker_observation_.Observe(scanner_broker);
+  retroactive_pairing_detector_observation_.Observe(
+      retroactive_pairing_detector);
   pairer_broker_observation_.Observe(pairer_broker);
   ui_broker_observation_.Observe(ui_broker);
 }
 
 QuickPairMetricsLogger::~QuickPairMetricsLogger() = default;
 
+void QuickPairMetricsLogger::OnGetAdapter(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  adapter_ = adapter;
+  adapter_observation_.Observe(adapter_.get());
+}
+
+void QuickPairMetricsLogger::DevicePairedChanged(
+    device::BluetoothAdapter* adapter,
+    device::BluetoothDevice* device,
+    bool new_paired_status) {
+  // This event fires whenever a device pairing has changed with the adapter.
+  // If the |new_paired_status| is false, it means a device was unpaired with
+  // the adapter, so we early return since it would not be a device that has
+  // been paired alternatively. If the device that was paired to that fires this
+  // event is a device we just paired to with Fast Pair, then we early return
+  // since it also wouldn't be one that was alternatively pair to. We want to
+  // only continue our check here if we have a newly paired device that was
+  // paired with classic Bluetooth pairing.
+  const std::string& classic_address = device->GetAddress();
+  if (!new_paired_status ||
+      base::Contains(fast_pair_addresses_, classic_address)) {
+    return;
+  }
+
+  RecordPairingMethod(PairingMethod::kSystemPairingUi);
+}
+
 void QuickPairMetricsLogger::OnDevicePaired(scoped_refptr<Device> device) {
   AttemptRecordingFastPairEngagementFlow(
       *device, FastPairEngagementFlowEvent::kPairingSucceeded);
   feature_usage_metrics_logger_->RecordUsage(/*success=*/true);
+
   base::TimeDelta total_pair_time =
       base::TimeTicks::Now() - device_pairing_start_timestamps_[device];
   AttemptRecordingTotalUxPairTime(*device, total_pair_time);
+
+  RecordPairingMethod(PairingMethod::kFastPair);
+
+  // The classic address is assigned to the Device during the
+  // initial Fast Pair pairing protocol during the key exchange, and if it
+  // doesn't exist, then it wasn't properly paired during initial Fast Pair
+  // pairing. We want to save the addresses here in the event that the
+  // Bluetooth adapter pairing event fires, so we can detect when a device
+  // was paired solely via classic bluetooth, instead of Fast Pair.
+  if (device->classic_address())
+    fast_pair_addresses_.insert(device->classic_address().value());
 }
 
 void QuickPairMetricsLogger::OnPairFailure(scoped_refptr<Device> device,
@@ -82,6 +130,73 @@
       *device, FastPairEngagementFlowEvent::kDiscoveryUiShown);
 }
 
+void QuickPairMetricsLogger::OnRetroactivePairFound(
+    scoped_refptr<Device> device) {
+  AttemptRecordingFastPairRetroactiveEngagementFlow(
+      *device,
+      FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown);
+}
+
+void QuickPairMetricsLogger::OnAssociateAccountAction(
+    scoped_refptr<Device> device,
+    AssociateAccountAction action) {
+  switch (action) {
+    case AssociateAccountAction::kAssoicateAccount:
+      if (base::Contains(learn_more_devices_, device)) {
+        AttemptRecordingFastPairRetroactiveEngagementFlow(
+            *device, FastPairRetroactiveEngagementFlowEvent::
+                         kAssociateAccountSavePressedAfterLearnMorePressed);
+        learn_more_devices_.erase(device);
+        break;
+      }
+
+      AttemptRecordingFastPairRetroactiveEngagementFlow(
+          *device,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed);
+      break;
+    case AssociateAccountAction::kLearnMore:
+      // We need to record whether or not the Associate Account UI for this
+      // device has had the Learn More button pressed because since the
+      // Learn More button is not a terminal state, we need to record
+      // if the subsequent terminal states were reached after the user
+      // has learned more about saving their accounts. So we will check
+      // this map when the user dismisses or saves their account in order
+      // to capture whether or not the user elected to learn more beforehand.
+      learn_more_devices_.insert(device);
+
+      AttemptRecordingFastPairRetroactiveEngagementFlow(
+          *device, FastPairRetroactiveEngagementFlowEvent::
+                       kAssociateAccountLearnMorePressed);
+      break;
+    case AssociateAccountAction::kDismissedByUser:
+      if (base::Contains(learn_more_devices_, device)) {
+        AttemptRecordingFastPairRetroactiveEngagementFlow(
+            *device, FastPairRetroactiveEngagementFlowEvent::
+                         kAssociateAccountDismissedByUserAfterLearnMorePressed);
+        learn_more_devices_.erase(device);
+        break;
+      }
+
+      AttemptRecordingFastPairRetroactiveEngagementFlow(
+          *device, FastPairRetroactiveEngagementFlowEvent::
+                       kAssociateAccountUiDismissedByUser);
+      break;
+    case AssociateAccountAction::kDismissed:
+      if (base::Contains(learn_more_devices_, device)) {
+        AttemptRecordingFastPairRetroactiveEngagementFlow(
+            *device, FastPairRetroactiveEngagementFlowEvent::
+                         kAssociateAccountDismissedAfterLearnMorePressed);
+        learn_more_devices_.erase(device);
+        break;
+      }
+
+      AttemptRecordingFastPairRetroactiveEngagementFlow(
+          *device,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed);
+      break;
+  }
+}
+
 void QuickPairMetricsLogger::OnAccountKeyWrite(
     scoped_refptr<Device> device,
     absl::optional<AccountKeyFailure> error) {}
@@ -89,10 +204,6 @@
 void QuickPairMetricsLogger::OnCompanionAppAction(scoped_refptr<Device> device,
                                                   CompanionAppAction action) {}
 
-void QuickPairMetricsLogger::OnAssociateAccountAction(
-    scoped_refptr<Device> device,
-    AssociateAccountAction action) {}
-
 void QuickPairMetricsLogger::OnDeviceLost(scoped_refptr<Device> device) {}
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h
index c7f289546..e0f6ea58 100644
--- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h
+++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger.h
@@ -6,12 +6,16 @@
 #define ASH_QUICK_PAIR_KEYED_SERVICE_QUICK_PAIR_METRICS_LOGGER_H_
 
 #include "ash/quick_pair/pairing/pairer_broker.h"
+#include "ash/quick_pair/pairing/retroactive_pairing_detector.h"
 #include "ash/quick_pair/scanning/scanner_broker.h"
 #include "ash/quick_pair/ui/ui_broker.h"
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/time/time.h"
+#include "device/bluetooth/bluetooth_adapter.h"
 
 namespace ash {
 namespace quick_pair {
@@ -22,11 +26,15 @@
 // Observes pairing, scanning and UI events and logs corresponding metrics.
 class QuickPairMetricsLogger : public PairerBroker::Observer,
                                public ScannerBroker::Observer,
-                               public UIBroker::Observer {
+                               public UIBroker::Observer,
+                               public RetroactivePairingDetector::Observer,
+                               public device::BluetoothAdapter::Observer {
  public:
-  QuickPairMetricsLogger(ScannerBroker* scanner_broker,
-                         PairerBroker* pairer_broker,
-                         UIBroker* ui_broker);
+  QuickPairMetricsLogger(
+      ScannerBroker* scanner_broker,
+      PairerBroker* pairer_broker,
+      UIBroker* ui_broker,
+      RetroactivePairingDetector* retroactive_pairing_detector);
   QuickPairMetricsLogger(const QuickPairMetricsLogger&) = delete;
   QuickPairMetricsLogger& operator=(const QuickPairMetricsLogger&) = delete;
   ~QuickPairMetricsLogger() override;
@@ -53,20 +61,52 @@
   void OnDeviceFound(scoped_refptr<Device> device) override;
   void OnDeviceLost(scoped_refptr<Device> device) override;
 
+  // RetroactivePairingDetector::Observer
+  void OnRetroactivePairFound(scoped_refptr<Device> device) override;
+
+  // device::BluetoothAdapter::Observer
+  void DevicePairedChanged(device::BluetoothAdapter* adapter,
+                           device::BluetoothDevice* device,
+                           bool new_paired_status) override;
+
+  // Internal method called by BluetoothAdapterFactory to provide the adapter
+  // object.
+  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+
   // Map of devices to the time at which a pairing was initiated. This is used
   // to calculate the time between the user electing to pair the device and
   // the pairing entering a terminal state (success or failure).
   base::flat_map<scoped_refptr<Device>, base::TimeTicks>
       device_pairing_start_timestamps_;
 
+  // Set of devices of which on the Associate Account UI shown, the Learn More
+  // button was pressed. We need this map to know which
+  // |FastPairRetroactiveEngagementFlowEvent| event to log at the subsequent
+  // AssociateAccount action events that will follow, since the LearnMore
+  // event is not a terminal state.
+  base::flat_set<scoped_refptr<Device>> learn_more_devices_;
+
+  // The classic pairing addresses of Fast Pair devices that we have already
+  // paired to.
+  base::flat_set<std::string> fast_pair_addresses_;
+
+  scoped_refptr<device::BluetoothAdapter> adapter_;
   std::unique_ptr<FastPairFeatureUsageMetricsLogger>
       feature_usage_metrics_logger_;
+
+  base::ScopedObservation<device::BluetoothAdapter,
+                          device::BluetoothAdapter::Observer>
+      adapter_observation_{this};
   base::ScopedObservation<ScannerBroker, ScannerBroker::Observer>
       scanner_broker_observation_{this};
   base::ScopedObservation<PairerBroker, PairerBroker::Observer>
       pairer_broker_observation_{this};
+  base::ScopedObservation<RetroactivePairingDetector,
+                          RetroactivePairingDetector::Observer>
+      retroactive_pairing_detector_observation_{this};
   base::ScopedObservation<UIBroker, UIBroker::Observer> ui_broker_observation_{
       this};
+  base::WeakPtrFactory<QuickPairMetricsLogger> weak_ptr_factory_{this};
 };
 
 }  // namespace quick_pair
diff --git a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
index 9c4735a..5f326ba 100644
--- a/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_metrics_logger_unittest.cc
@@ -6,13 +6,16 @@
 
 #include <memory>
 
+#include "ash/quick_pair/common/constants.h"
 #include "ash/quick_pair/common/device.h"
 #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
 #include "ash/quick_pair/common/logging.h"
 #include "ash/quick_pair/common/pair_failure.h"
 #include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/pairing/fake_retroactive_pairing_detector.h"
 #include "ash/quick_pair/pairing/mock_pairer_broker.h"
 #include "ash/quick_pair/pairing/pairer_broker.h"
+#include "ash/quick_pair/pairing/retroactive_pairing_detector.h"
 #include "ash/quick_pair/scanning/mock_scanner_broker.h"
 #include "ash/quick_pair/scanning/scanner_broker.h"
 #include "ash/quick_pair/ui/mock_ui_broker.h"
@@ -22,6 +25,9 @@
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -34,10 +40,46 @@
 constexpr char kFastPairEngagementFlowMetricSubsequent[] =
     "Bluetooth.ChromeOS.FastPair.EngagementFunnel.Steps."
     "SubsequentPairingProtocol";
+const char kFastPairRetroactiveEngagementFlowMetric[] =
+    "Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps";
 constexpr char kFastPairPairTimeMetricInitial[] =
     "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.InitialPairingProtocol";
 constexpr char kFastPairPairTimeMetricSubsequent[] =
     "Bluetooth.ChromeOS.FastPair.TotalUxPairTime.SubsequentPairingProtocol";
+const char kPairingMethodMetric[] = "Bluetooth.ChromeOS.FastPair.PairingMethod";
+
+constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16";
+constexpr char kTestBleDeviceName[] = "Test Device Name";
+constexpr char kValidModelId[] = "718c17";
+
+std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+CreateTestBluetoothDevice(std::string address) {
+  return std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
+      /*adapter=*/nullptr, /*bluetooth_class=*/0, kTestBleDeviceName, address,
+      /*paired=*/true, /*connected=*/false);
+}
+
+class FakeMetricBluetoothAdapter
+    : public testing::NiceMock<device::MockBluetoothAdapter> {
+ public:
+  device::BluetoothDevice* GetDevice(const std::string& address) override {
+    for (const auto& it : mock_devices_) {
+      if (it->GetAddress() == address)
+        return it.get();
+    }
+
+    return nullptr;
+  }
+
+  void NotifyDevicePairedChanged(device::BluetoothDevice* device,
+                                 bool new_paired_status) {
+    device::BluetoothAdapter::NotifyDevicePairedChanged(device,
+                                                        new_paired_status);
+  }
+
+ private:
+  ~FakeMetricBluetoothAdapter() = default;
+};
 
 }  // namespace
 
@@ -47,10 +89,19 @@
 class QuickPairMetricsLoggerTest : public testing::Test {
  public:
   void SetUp() override {
+    adapter_ = base::MakeRefCounted<FakeMetricBluetoothAdapter>();
+    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
+
     scanner_broker_ = std::make_unique<MockScannerBroker>();
     mock_scanner_broker_ =
         static_cast<MockScannerBroker*>(scanner_broker_.get());
 
+    retroactive_pairing_detector_ =
+        std::make_unique<FakeRetroactivePairingDetector>();
+    fake_retroactive_pairing_detector_ =
+        static_cast<FakeRetroactivePairingDetector*>(
+            retroactive_pairing_detector_.get());
+
     pairer_broker_ = std::make_unique<MockPairerBroker>();
     mock_pairer_broker_ = static_cast<MockPairerBroker*>(pairer_broker_.get());
 
@@ -61,9 +112,12 @@
         kTestMetadataId, kTestAddress, Protocol::kFastPairInitial);
     subsequent_device_ = base::MakeRefCounted<Device>(
         kTestMetadataId, kTestAddress, Protocol::kFastPairSubsequent);
+    retroactive_device_ = base::MakeRefCounted<Device>(
+        kTestMetadataId, kTestAddress, Protocol::kFastPairRetroactive);
 
     metrics_logger_ = std::make_unique<QuickPairMetricsLogger>(
-        scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get());
+        scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get(),
+        retroactive_pairing_detector_.get());
   }
 
   void SimulateDiscoveryUiShown(Protocol protocol) {
@@ -129,9 +183,11 @@
   void SimulatePairingSucceeded(Protocol protocol) {
     switch (protocol) {
       case Protocol::kFastPairInitial:
+        initial_device_->set_classic_address(kTestAddress);
         mock_pairer_broker_->NotifyDevicePaired(initial_device_);
         break;
       case Protocol::kFastPairSubsequent:
+        subsequent_device_->set_classic_address(kTestAddress);
         mock_pairer_broker_->NotifyDevicePaired(subsequent_device_);
         break;
       case Protocol::kFastPairRetroactive:
@@ -169,19 +225,65 @@
     }
   }
 
+  void SimulateAssociateAccountUiShown() {
+    fake_retroactive_pairing_detector_->NotifyRetroactivePairFound(
+        retroactive_device_);
+  }
+
+  void SimulateAssociateAccountUiDismissed() {
+    mock_ui_broker_->NotifyAssociateAccountAction(
+        retroactive_device_, AssociateAccountAction::kDismissed);
+  }
+
+  void SimulateAssociateAccountUiDismissedByUser() {
+    mock_ui_broker_->NotifyAssociateAccountAction(
+        retroactive_device_, AssociateAccountAction::kDismissedByUser);
+  }
+
+  void SimulateAssociateAccountUiSavePressed() {
+    mock_ui_broker_->NotifyAssociateAccountAction(
+        retroactive_device_, AssociateAccountAction::kAssoicateAccount);
+  }
+
+  void SimulateAssociateAccountUiLearnMorePressed() {
+    mock_ui_broker_->NotifyAssociateAccountAction(
+        retroactive_device_, AssociateAccountAction::kLearnMore);
+  }
+
+  void PairFastPairDeviceWithFastPair(std::string address) {
+    auto fp_device = base::MakeRefCounted<Device>(kValidModelId, address,
+                                                  Protocol::kFastPairInitial);
+    fp_device->set_classic_address(address);
+    mock_pairer_broker_->NotifyDevicePaired(fp_device);
+  }
+
+  void PairFastPairDeviceWithClassicBluetooth(bool new_paired_status,
+                                              std::string classic_address) {
+    std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+        bluetooth_device = CreateTestBluetoothDevice(classic_address);
+    bluetooth_device->AddUUID(ash::quick_pair::kFastPairBluetoothUuid);
+    auto* bt_device_ptr = bluetooth_device.get();
+    adapter_->AddMockDevice(std::move(bluetooth_device));
+    adapter_->NotifyDevicePairedChanged(bt_device_ptr, new_paired_status);
+  }
+
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  protected:
   base::HistogramTester histogram_tester_;
   base::test::SingleThreadTaskEnvironment task_environment_;
+  scoped_refptr<FakeMetricBluetoothAdapter> adapter_;
   scoped_refptr<Device> initial_device_;
   scoped_refptr<Device> subsequent_device_;
+  scoped_refptr<Device> retroactive_device_;
 
   MockScannerBroker* mock_scanner_broker_ = nullptr;
   MockPairerBroker* mock_pairer_broker_ = nullptr;
   MockUIBroker* mock_ui_broker_ = nullptr;
+  FakeRetroactivePairingDetector* fake_retroactive_pairing_detector_ = nullptr;
 
   std::unique_ptr<ScannerBroker> scanner_broker_;
+  std::unique_ptr<RetroactivePairingDetector> retroactive_pairing_detector_;
   std::unique_ptr<PairerBroker> pairer_broker_;
   std::unique_ptr<UIBroker> ui_broker_;
   std::unique_ptr<QuickPairMetricsLogger> metrics_logger_;
@@ -669,5 +771,426 @@
   histogram_tester().ExpectTotalCount(kFastPairPairTimeMetricSubsequent, 1);
 }
 
+TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountShown) {
+  SimulateAssociateAccountUiShown();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      1);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountDismissed) {
+  SimulateAssociateAccountUiDismissed();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountDismissedByUser) {
+  SimulateAssociateAccountUiDismissedByUser();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            1);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountSavePressed) {
+  SimulateAssociateAccountUiSavePressed();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, LogAssociateAccountLearnMorePressed) {
+  SimulateAssociateAccountUiLearnMorePressed();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest,
+       LogAssociateAccountLearnMorePressed_SavePressed) {
+  SimulateAssociateAccountUiLearnMorePressed();
+  base::RunLoop().RunUntilIdle();
+  SimulateAssociateAccountUiSavePressed();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest,
+       LogAssociateAccountLearnMorePressed_Dismissed) {
+  SimulateAssociateAccountUiLearnMorePressed();
+  base::RunLoop().RunUntilIdle();
+  SimulateAssociateAccountUiDismissed();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            1);
+}
+
+TEST_F(QuickPairMetricsLoggerTest,
+       LogAssociateAccountLearnMorePressed_DismissedByUser) {
+  SimulateAssociateAccountUiLearnMorePressed();
+  base::RunLoop().RunUntilIdle();
+  SimulateAssociateAccountUiDismissedByUser();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown),
+      0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountUiDismissedByUser),
+            0);
+  EXPECT_EQ(
+      histogram_tester().GetBucketCount(
+          kFastPairRetroactiveEngagementFlowMetric,
+          FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed),
+      0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountSavePressedAfterLearnMorePressed),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedByUserAfterLearnMorePressed),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(
+                kFastPairRetroactiveEngagementFlowMetric,
+                FastPairRetroactiveEngagementFlowEvent::
+                    kAssociateAccountDismissedAfterLearnMorePressed),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, DevicedPaired_FastPair) {
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            0);
+  PairFastPairDeviceWithFastPair(kTestDeviceAddress);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            1);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, DeviceUnpaired) {
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            0);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/false, kTestDeviceAddress);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            0);
+}
+
+TEST_F(QuickPairMetricsLoggerTest, DevicePaired) {
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            0);
+  PairFastPairDeviceWithClassicBluetooth(
+      /*new_paired_status=*/true, kTestDeviceAddress);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kFastPair),
+            0);
+  EXPECT_EQ(histogram_tester().GetBucketCount(kPairingMethodMetric,
+                                              PairingMethod::kSystemPairingUi),
+            1);
+}
+
 }  // namespace quick_pair
 }  // namespace ash
diff --git a/ash/system/message_center/unified_message_center_bubble.cc b/ash/system/message_center/unified_message_center_bubble.cc
index 5a6b32c..5224e313 100644
--- a/ash/system/message_center/unified_message_center_bubble.cc
+++ b/ash/system/message_center/unified_message_center_bubble.cc
@@ -127,6 +127,11 @@
   CHECK(!views::WidgetObserver::IsInObserverList());
 }
 
+gfx::Rect UnifiedMessageCenterBubble::GetBoundsInScreen() const {
+  DCHECK(bubble_view_);
+  return bubble_view_->GetBoundsInScreen();
+}
+
 void UnifiedMessageCenterBubble::CollapseMessageCenter() {
   if (message_center_view_->collapsed())
     return;
diff --git a/ash/system/message_center/unified_message_center_bubble.h b/ash/system/message_center/unified_message_center_bubble.h
index 14da351..f506602 100644
--- a/ash/system/message_center/unified_message_center_bubble.h
+++ b/ash/system/message_center/unified_message_center_bubble.h
@@ -39,6 +39,9 @@
 
   ~UnifiedMessageCenterBubble() override;
 
+  // Return the bounds of the bubble in the screen.
+  gfx::Rect GetBoundsInScreen() const;
+
   // We need the code to show the bubble explicitly separated from the
   // contructor. This is to prevent trigerring the TrayEventFilter from within
   // the constructor. Doing so can cause a crash when the TrayEventFilter tries
diff --git a/ash/system/message_center/unified_message_center_bubble_unittest.cc b/ash/system/message_center/unified_message_center_bubble_unittest.cc
index 51fc252d..d11a129 100644
--- a/ash/system/message_center/unified_message_center_bubble_unittest.cc
+++ b/ash/system/message_center/unified_message_center_bubble_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -16,7 +17,9 @@
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/unified_system_tray_view.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
 #include "components/prefs/pref_service.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "ui/message_center/message_center.h"
@@ -396,4 +399,79 @@
             GetFirstQuickSettingsFocusable());
 }
 
+// Tests with NotificationsRefresh enabled and disabled.
+class ParameterizedMessageCenterBubbleTest
+    : public UnifiedMessageCenterBubbleTest,
+      public testing::WithParamInterface<bool> {
+ public:
+  ParameterizedMessageCenterBubbleTest() = default;
+
+  ParameterizedMessageCenterBubbleTest(
+      const ParameterizedMessageCenterBubbleTest&) = delete;
+  ParameterizedMessageCenterBubbleTest& operator=(
+      const ParameterizedMessageCenterBubbleTest&) = delete;
+
+  ~ParameterizedMessageCenterBubbleTest() override = default;
+
+  // AshTestBase:
+  void SetUp() override {
+    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
+    scoped_feature_list_->InitWithFeatureState(features::kNotificationsRefresh,
+                                               IsNotificationsRefreshEnabled());
+
+    UnifiedMessageCenterBubbleTest::SetUp();
+  }
+
+  bool IsNotificationsRefreshEnabled() const { return GetParam(); }
+
+ private:
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         ParameterizedMessageCenterBubbleTest,
+                         testing::Bool() /* IsNotificationsRefreshEnabled() */);
+
+TEST_P(ParameterizedMessageCenterBubbleTest, BubbleBounds) {
+  // Set display size where the message center is not collapsed.
+  UpdateDisplay("0+0-1280×1024");
+
+  // Ensure message center is not collapsed.
+  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  ASSERT_FALSE(GetMessageCenterBubble()->IsMessageCenterCollapsed());
+
+  // Add enough notifications so that the scroll bar is visible.
+  while (!GetMessageCenterBubble()->message_center_view()->IsScrollBarVisible())
+    AddNotification();
+
+  // The message center bubble should be positioned above the system tray
+  // bubble.
+  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(),
+            GetSystemTrayBubble()->GetBoundsInScreen().y());
+  GetPrimaryUnifiedSystemTray()->CloseBubble();
+
+  // Go into overview mode, check bounds again.
+  EnterOverview();
+  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(),
+            GetSystemTrayBubble()->GetBoundsInScreen().y());
+  GetPrimaryUnifiedSystemTray()->CloseBubble();
+  ExitOverview();
+
+  // Go into tablet mode, check bounds again.
+  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
+  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(),
+            GetSystemTrayBubble()->GetBoundsInScreen().y());
+  GetPrimaryUnifiedSystemTray()->CloseBubble();
+
+  // Go into overview mode inside tablet mode, check bounds again.
+  EnterOverview();
+  GetPrimaryUnifiedSystemTray()->ShowBubble();
+  EXPECT_LT(GetMessageCenterBubble()->GetBoundsInScreen().bottom(),
+            GetSystemTrayBubble()->GetBoundsInScreen().y());
+  GetPrimaryUnifiedSystemTray()->CloseBubble();
+}
+
 }  // namespace ash
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index 72db4a4..23dac8d1 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -129,8 +129,12 @@
 
 void UnifiedMessageCenterView::SetMaxHeight(int max_height) {
   int max_scroller_height = max_height;
-  if (notification_bar_->GetVisible())
+  if (notification_bar_->GetVisible()) {
     max_scroller_height -= kStackedNotificationBarHeight;
+    if (is_notifications_refresh_enabled_)
+      max_scroller_height -=
+          2 * kNotificationBarVerticalPadding + kMessageCenterBottomPadding;
+  }
   scroller_->ClipHeightTo(0, max_scroller_height);
 }
 
@@ -183,6 +187,10 @@
   return notification_bar_->GetVisible();
 }
 
+bool UnifiedMessageCenterView::IsScrollBarVisible() const {
+  return scroll_bar_->GetVisible();
+}
+
 void UnifiedMessageCenterView::OnNotificationSlidOut() {
   if (notification_bar_->GetVisible()) {
     notification_bar_->Update(
diff --git a/ash/system/message_center/unified_message_center_view.h b/ash/system/message_center/unified_message_center_view.h
index 52d3301..4729718 100644
--- a/ash/system/message_center/unified_message_center_view.h
+++ b/ash/system/message_center/unified_message_center_view.h
@@ -130,6 +130,9 @@
   // Returns true if the notification bar is visible.
   bool IsNotificationBarVisible() const;
 
+  // Returns true if the scroll bar is visible.
+  bool IsScrollBarVisible() const;
+
   // views::View:
   void AddedToWidget() override;
   void RemovedFromWidget() override;
diff --git a/ash/system/message_center/unified_message_list_view.cc b/ash/system/message_center/unified_message_list_view.cc
index bb5674f..0f5f737 100644
--- a/ash/system/message_center/unified_message_list_view.cc
+++ b/ash/system/message_center/unified_message_list_view.cc
@@ -946,7 +946,10 @@
           kExpandOrCollapseAnimationSmoothnessHistogramName);
       DCHECK(expand_or_collapsing_container_);
       animation_duration =
-          expand_or_collapsing_container_->GetBoundsAnimationDuration();
+          expand_or_collapsing_container_
+              ? expand_or_collapsing_container_->GetBoundsAnimationDuration()
+              : base::Milliseconds(
+                    kLargeImageExpandAndCollapseAnimationDuration);
       break;
   }
 
diff --git a/ash/webui/resources/BUILD.gn b/ash/webui/resources/BUILD.gn
index 60aff14..427f9cc 100644
--- a/ash/webui/resources/BUILD.gn
+++ b/ash/webui/resources/BUILD.gn
@@ -76,6 +76,11 @@
   deps = [ "//ash/webui/firmware_update_ui/resources:build_grd" ]
 }
 
+ash_generated_grit("system_extensions_internals_resources") {
+  source = "$root_gen_dir/ash/webui/system_extensions_internals_ui/ash_system_extensions_internals_resources.grd"
+  deps = [ "//ash/webui/system_extensions_internals_ui:build_grd" ]
+}
+
 if (!is_official_build) {
   # Resources used by chrome://demo-mode-app
   ash_generated_grit("demo_mode_app_resources") {
diff --git a/ash/webui/system_extensions_internals_ui/BUILD.gn b/ash/webui/system_extensions_internals_ui/BUILD.gn
new file mode 100644
index 0000000..d260625a
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/BUILD.gn
@@ -0,0 +1,55 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/chromeos/ui_mode.gni")
+import("//chrome/test/base/js2gtest.gni")
+import("//third_party/closure_compiler/compile_js.gni")
+import("//ui/webui/resources/tools/generate_grd.gni")
+
+assert(is_chromeos_ash, "System Extensions Internals is ash-chrome only")
+
+static_library("system_extensions_internals_ui") {
+  sources = [
+    "system_extensions_internals_ui.cc",
+    "system_extensions_internals_ui.h",
+    "url_constants.cc",
+    "url_constants.h",
+  ]
+
+  deps = [
+    "//ash/webui/resources:system_extensions_internals_resources",
+    "//content/public/browser",
+    "//ui/webui",
+  ]
+}
+
+js_type_check("closure_compile") {
+  deps = [ ":system_extensions_internals" ]
+  closure_flags = default_closure_args + mojom_js_args
+}
+
+js_library("system_extensions_internals") {
+  sources = [ "resources/index.js" ]
+}
+
+js2gtest("browser_tests_js") {
+  test_type = "mojo_lite_webui"
+
+  sources = [ "test/system_extensions_internals_ui_browsertest.js" ]
+
+  defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
+}
+
+grd_prefix = "ash_system_extensions_internals"
+
+generate_grd("build_grd") {
+  input_files_base_dir = rebase_path("resources", "//")
+  input_files = [
+    "index.html",
+    "index.js",
+  ]
+
+  grd_prefix = grd_prefix
+  out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
+}
diff --git a/ash/webui/system_extensions_internals_ui/DEPS b/ash/webui/system_extensions_internals_ui/DEPS
new file mode 100644
index 0000000..e1a02ef
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  # Do not add chrome here. Use a delegate instead.
+  "+ash/grit/ash_system_extensions_internals_resources.h",
+  "+ui/webui",
+]
diff --git a/ash/webui/system_extensions_internals_ui/OWNERS b/ash/webui/system_extensions_internals_ui/OWNERS
new file mode 100644
index 0000000..47d56c85
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/OWNERS
@@ -0,0 +1 @@
+file://ash/webui/system_apps/PLATFORM_OWNERS
diff --git a/ash/webui/system_extensions_internals_ui/resources/index.html b/ash/webui/system_extensions_internals_ui/resources/index.html
new file mode 100644
index 0000000..1268eca
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/resources/index.html
@@ -0,0 +1,16 @@
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<!DOCTYPE html>
+<head>
+  <meta charset="utf-8">
+  <title>System Extensions Internals</title>
+</head>
+<p>Choose a directory with a System Extension to sideload it. The System Extension directory must be in Downloads for Chrome OS to be able to find it.</p>
+<button id="choose-directory">Choose directory</button>
+<dialog id="result-dialog">
+  <p>System Extension installed</p>
+</dialog>
+<!-- Needed for js browser tests -->
+<script src="chrome://resources/mojo/mojo/public/js/mojo_bindings_lite.js"></script>
+<script type="module" src="index.js"></script>
diff --git a/ash/webui/system_extensions_internals_ui/resources/index.js b/ash/webui/system_extensions_internals_ui/resources/index.js
new file mode 100644
index 0000000..1441a844
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/resources/index.js
@@ -0,0 +1,10 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const chooseDirButton = document.querySelector('#choose-directory');
+const resultDialog = document.querySelector('#result-dialog');
+
+chooseDirButton.addEventListener('click', async event => {
+  resultDialog.showModal();
+});
diff --git a/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc
new file mode 100644
index 0000000..69458872
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.cc
@@ -0,0 +1,38 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h"
+
+#include "ash/grit/ash_system_extensions_internals_resources.h"
+#include "ash/grit/ash_system_extensions_internals_resources_map.h"
+#include "ash/webui/system_extensions_internals_ui/url_constants.h"
+#include "base/memory/ptr_util.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_ui.h"
+#include "content/public/browser/web_ui_data_source.h"
+#include "services/network/public/mojom/content_security_policy.mojom.h"
+#include "ui/webui/webui_allowlist.h"
+
+namespace ash {
+
+SystemExtensionsInternalsUI::SystemExtensionsInternalsUI(content::WebUI* web_ui)
+    : ui::MojoWebUIController(web_ui) {
+  auto data_source = base::WrapUnique(
+      content::WebUIDataSource::Create(kChromeUISystemExtensionsInternalsHost));
+
+  data_source->AddResourcePath("",
+                               IDR_ASH_SYSTEM_EXTENSIONS_INTERNALS_INDEX_HTML);
+  data_source->AddResourcePaths(
+      base::make_span(kAshSystemExtensionsInternalsResources,
+                      kAshSystemExtensionsInternalsResourcesSize));
+
+  auto* browser_context = web_ui->GetWebContents()->GetBrowserContext();
+  content::WebUIDataSource::Add(browser_context, data_source.release());
+}
+
+SystemExtensionsInternalsUI::~SystemExtensionsInternalsUI() = default;
+
+WEB_UI_CONTROLLER_TYPE_IMPL(SystemExtensionsInternalsUI)
+
+}  // namespace ash
diff --git a/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h
new file mode 100644
index 0000000..c1dcb1c
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h
@@ -0,0 +1,27 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_
+#define ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_
+
+#include "ui/webui/mojo_web_ui_controller.h"
+
+namespace ash {
+
+// WebUIController for chrome://system-extensions-internals/.
+class SystemExtensionsInternalsUI : public ui::MojoWebUIController {
+ public:
+  explicit SystemExtensionsInternalsUI(content::WebUI* web_ui);
+  SystemExtensionsInternalsUI(const SystemExtensionsInternalsUI&) = delete;
+  SystemExtensionsInternalsUI& operator=(const SystemExtensionsInternalsUI&) =
+      delete;
+  ~SystemExtensionsInternalsUI() override;
+
+ private:
+  WEB_UI_CONTROLLER_TYPE_DECL();
+};
+
+}  // namespace ash
+
+#endif  // ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_SYSTEM_EXTENSIONS_INTERNALS_UI_H_
diff --git a/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js b/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js
new file mode 100644
index 0000000..e9f0658
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/test/system_extensions_internals_ui_browsertest.js
@@ -0,0 +1,52 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Test suite for chrome://system-extensions-internals/
+ */
+
+GEN('#include "ash/constants/ash_features.h"');
+GEN('#include "content/public/test/browser_test.h"');
+
+const HOST_ORIGIN = 'chrome://system-extensions-internals';
+
+// TODO:(crbug.com/1262025): We should avoid using `var`.
+//
+// js2gtest fixtures require var here (https://crbug.com/1033337).
+// eslint-disable-next-line no-var
+var SystemExtensionsInternalsUIBrowserTest = class extends testing.Test {
+  /** @override */
+  get browsePreload() {
+    return HOST_ORIGIN;
+  }
+
+  /** @override */
+  get featureList() {
+    return {
+      enabled: [
+        'ash::features::kSystemExtensions',
+      ]
+    };
+  }
+
+  /** @override */
+  get runAccessibilityChecks() {
+    return false;
+  }
+
+  /** @override */
+  get isAsync() {
+    return true;
+  }
+};
+
+// Tests that chrome://system-extensions-internals loads successfully.
+TEST_F(
+    'SystemExtensionsInternalsUIBrowserTest', 'HasChromeSchemeURL',
+    async () => {
+      const header = document.querySelector('title');
+      assertEquals(header.innerText, 'System Extensions Internals');
+      assertEquals(document.location.origin, HOST_ORIGIN);
+      testDone();
+    });
diff --git a/ash/webui/system_extensions_internals_ui/url_constants.cc b/ash/webui/system_extensions_internals_ui/url_constants.cc
new file mode 100644
index 0000000..4838257
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/url_constants.cc
@@ -0,0 +1,10 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/system_extensions_internals_ui/url_constants.h"
+
+namespace ash {
+const char kChromeUISystemExtensionsInternalsHost[] =
+    "system-extensions-internals";
+}  // namespace ash
diff --git a/ash/webui/system_extensions_internals_ui/url_constants.h b/ash/webui/system_extensions_internals_ui/url_constants.h
new file mode 100644
index 0000000..bf293e9
--- /dev/null
+++ b/ash/webui/system_extensions_internals_ui/url_constants.h
@@ -0,0 +1,12 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_
+#define ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_
+
+namespace ash {
+extern const char kChromeUISystemExtensionsInternalsHost[];
+}  // namespace ash
+
+#endif  // ASH_WEBUI_SYSTEM_EXTENSIONS_INTERNALS_UI_URL_CONSTANTS_H_
diff --git a/ash/wm/desks/desk_animation_impl.cc b/ash/wm/desks/desk_animation_impl.cc
index 73a507cc..ca8bace7 100644
--- a/ash/wm/desks/desk_animation_impl.cc
+++ b/ash/wm/desks/desk_animation_impl.cc
@@ -11,11 +11,13 @@
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/desks_util.h"
+#include "ash/wm/haptics_util.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/window_util.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
+#include "ui/events/devices/haptic_touchpad_effects.h"
 
 namespace ash {
 
@@ -156,6 +158,10 @@
 
   presentation_time_recorder_->RequestNext();
 
+  auto* first_animator = desk_switch_animators_.front().get();
+  DCHECK(first_animator);
+  const bool old_reached_edge = first_animator->reached_edge();
+
   // If any of the displays need a new screenshot while scrolling, take the
   // ending desk screenshot for all of them to keep them in sync.
   absl::optional<int> ending_desk_index;
@@ -167,15 +173,25 @@
   }
 
   // See if the animator of the first display has visibly changed desks. If so,
-  // update |visible_desk_changes_| for metrics collection purposes.
-  auto* first_animator = desk_switch_animators_.front().get();
-  DCHECK(first_animator);
+  // update `visible_desk_changes_` for metrics collection purposes. Also fire a
+  // haptic event if we have reached the edge, or the visible desk has changed.
   if (first_animator->starting_desk_screenshot_taken() &&
       first_animator->ending_desk_screenshot_taken()) {
     const int old_visible_desk_index = visible_desk_index_;
     visible_desk_index_ = first_animator->GetIndexOfMostVisibleDeskScreenshot();
-    if (visible_desk_index_ != old_visible_desk_index)
+    if (visible_desk_index_ != old_visible_desk_index) {
       ++visible_desk_changes_;
+      haptics_util::PlayHapticTouchpadEffect(
+          ui::HapticTouchpadEffect::kTick,
+          ui::HapticTouchpadEffectStrength::kMedium);
+    }
+
+    const bool reached_edge = first_animator->reached_edge();
+    if (reached_edge && !old_reached_edge) {
+      haptics_util::PlayHapticTouchpadEffect(
+          ui::HapticTouchpadEffect::kKnock,
+          ui::HapticTouchpadEffectStrength::kMedium);
+    }
   }
 
   // No screenshot needed.
diff --git a/ash/wm/desks/desk_animation_impl_unittest.cc b/ash/wm/desks/desk_animation_impl_unittest.cc
index 901c6e4..0c288edb 100644
--- a/ash/wm/desks/desk_animation_impl_unittest.cc
+++ b/ash/wm/desks/desk_animation_impl_unittest.cc
@@ -9,6 +9,7 @@
 #include "ash/wm/desks/desks_constants.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
+#include "ash/wm/desks/desks_test_util.h"
 #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
 #include "base/barrier_closure.h"
 #include "base/test/scoped_feature_list.h"
@@ -18,19 +19,6 @@
 
 namespace ash {
 
-namespace {
-
-void WaitEndingScreenshotTaken(DeskActivationAnimation* animation) {
-  base::RunLoop run_loop;
-  auto* desk_switch_animator =
-      animation->GetDeskSwitchAnimatorAtIndexForTesting(0);
-  RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator)
-      .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure());
-  run_loop.Run();
-}
-
-}  // namespace
-
 using DeskActivationAnimationTest = AshTestBase;
 
 // Tests that there is no crash when ending a swipe animation before the
@@ -102,17 +90,17 @@
   animation.set_skip_notify_controller_on_animation_finished_for_testing(true);
   animation.Launch();
 
-  WaitEndingScreenshotTaken(&animation);
+  WaitUntilEndingScreenshotTaken(&animation);
   EXPECT_EQ(0, animation.visible_desk_changes());
 
   // Swipe enough so that our third and fourth desk screenshots are taken, and
   // then swipe so that the fourth desk is fully shown. There should be 3
   // visible desk changes in total.
   animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
-  WaitEndingScreenshotTaken(&animation);
+  WaitUntilEndingScreenshotTaken(&animation);
 
   animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
-  WaitEndingScreenshotTaken(&animation);
+  WaitUntilEndingScreenshotTaken(&animation);
 
   animation.UpdateSwipeAnimation(-3 * kTouchpadSwipeLengthForDeskChange);
   EXPECT_EQ(3, animation.visible_desk_changes());
@@ -146,7 +134,7 @@
   animation.Launch();
 
   window.reset();
-  WaitEndingScreenshotTaken(&animation);
+  WaitUntilEndingScreenshotTaken(&animation);
 }
 
 // Tests that if a fast swipe is detected, we will still wait for the ending
@@ -185,7 +173,7 @@
   animation.EndSwipeAnimation();
 
   ASSERT_FALSE(desk_switch_animator->ending_desk_screenshot_taken());
-  WaitEndingScreenshotTaken(&animation);
+  WaitUntilEndingScreenshotTaken(&animation);
 
   // Tests that there is an animation after the ending screenshots have been
   // taken.
diff --git a/ash/wm/desks/desks_test_util.cc b/ash/wm/desks/desks_test_util.cc
index 65737921..1be7738 100644
--- a/ash/wm/desks/desks_test_util.cc
+++ b/ash/wm/desks/desks_test_util.cc
@@ -7,6 +7,7 @@
 #include "ash/shell.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desk_animation_base.h"
+#include "ash/wm/desks/desk_animation_impl.h"
 #include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
 #include "ash/wm/gestures/wm_gesture_handler.h"
@@ -148,4 +149,13 @@
   }
 }
 
+void WaitUntilEndingScreenshotTaken(DeskActivationAnimation* animation) {
+  base::RunLoop run_loop;
+  auto* desk_switch_animator =
+      animation->GetDeskSwitchAnimatorAtIndexForTesting(0);
+  RootWindowDeskSwitchAnimatorTestApi(desk_switch_animator)
+      .SetOnEndingScreenshotTakenCallback(run_loop.QuitClosure());
+  run_loop.Run();
+}
+
 }  // namespace ash
diff --git a/ash/wm/desks/desks_test_util.h b/ash/wm/desks/desks_test_util.h
index fc0dead..e50e6ef 100644
--- a/ash/wm/desks/desks_test_util.h
+++ b/ash/wm/desks/desks_test_util.h
@@ -16,6 +16,8 @@
 
 namespace ash {
 
+class DeskActivationAnimation;
+
 constexpr int kNumFingersForHighlight = 3;
 constexpr int kNumFingersForDesksSwitch = 4;
 
@@ -69,6 +71,9 @@
 void ScrollToSwitchDesks(bool scroll_left,
                          ui::test::EventGenerator* event_generator);
 
+// Wait until `animation`'s ending screenshot has been taken.
+void WaitUntilEndingScreenshotTaken(DeskActivationAnimation* animation);
+
 }  // namespace ash
 
 #endif  // ASH_WM_DESKS_DESKS_TEST_UTIL_H_
diff --git a/ash/wm/desks/root_window_desk_switch_animator.cc b/ash/wm/desks/root_window_desk_switch_animator.cc
index d65c2b15..5279b145 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.cc
+++ b/ash/wm/desks/root_window_desk_switch_animator.cc
@@ -271,6 +271,13 @@
   //  edge padding (i.e. translation of (-190, 0)).
   gfx::RectF transformed_animation_layer_bounds(animation_layer->bounds());
   transform.TransformRect(&transformed_animation_layer_bounds);
+
+  // `reached_edge_` becomes true if the user has scrolled `animation_layer` to
+  // its limits.
+  reached_edge_ =
+      transformed_animation_layer_bounds.x() == 0 ||
+      transformed_animation_layer_bounds.right() == root_window_size_.width();
+
   transformed_animation_layer_bounds.Inset(edge_padding_width_dp_, 0);
 
   const bool moving_left = scroll_delta_x < 0.f;
diff --git a/ash/wm/desks/root_window_desk_switch_animator.h b/ash/wm/desks/root_window_desk_switch_animator.h
index ac5f566..8f292ee 100644
--- a/ash/wm/desks/root_window_desk_switch_animator.h
+++ b/ash/wm/desks/root_window_desk_switch_animator.h
@@ -220,6 +220,7 @@
     return ending_desk_screenshot_taken_;
   }
   bool animation_finished() const { return animation_finished_; }
+  bool reached_edge() const { return reached_edge_; }
 
   // Begins phase (1) of the animation by taking a screenshot of the starting
   // desk content. Delegate::OnStartingDeskScreenshotTaken() will be called once
@@ -362,6 +363,10 @@
   // True when phase (3) finishes.
   bool animation_finished_ = false;
 
+  // True if during a continuous swipe, the user went all the way left or right
+  // and swiping in that direction will no longer update the UI.
+  bool reached_edge_ = false;
+
   // True while setting a new transform for chaining. If a animation is active,
   // calling SetTransform will trigger OnImplicitAnimationsCompleted. In these
   // cases we do not want to notify our delegate that the animation is finished.
diff --git a/ash/wm/haptics_util_unittest.cc b/ash/wm/haptics_util_unittest.cc
index aae5384..3ae71b51 100644
--- a/ash/wm/haptics_util_unittest.cc
+++ b/ash/wm/haptics_util_unittest.cc
@@ -8,8 +8,12 @@
 
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
+#include "ash/wm/desks/desk_animation_impl.h"
+#include "ash/wm/desks/desks_constants.h"
 #include "ash/wm/desks/desks_controller.h"
+#include "ash/wm/desks/desks_histogram_enums.h"
 #include "ash/wm/desks/desks_test_util.h"
+#include "ash/wm/desks/root_window_desk_switch_animator_test_api.h"
 #include "ash/wm/gestures/wm_gesture_handler.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_item.h"
@@ -305,4 +309,75 @@
                    HapticTouchpadEffectStrength::kMedium));
 }
 
+// Tests that haptics are sent when doing a continuous touchpad gesture to
+// switch desks. They are expected to be sent if we hit the edge, or when the
+// visible desk changes.
+TEST_F(HapticsUtilTest, HapticFeedbackForContinuousDesksSwitching) {
+  auto input_controller = std::make_unique<InputControllerForTesting>();
+  haptics_util::SetInputControllerForTesting(input_controller.get());
+
+  // Add three desks for a total of four.
+  auto* desks_controller = DesksController::Get();
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+  desks_controller->NewDesk(DesksCreationRemovalSource::kButton);
+
+  // Create a standalone animation object. This is the same object that gets
+  // created when swiping with 4 fingers, but mocking 4 fingers swipes is harder
+  // to control in a test with all the async operations and touchpad unit
+  // conversions.
+  DeskActivationAnimation animation(desks_controller, 0, 1,
+                                    DesksSwitchSource::kDeskSwitchTouchpad,
+                                    /*update_window_activation=*/false);
+  animation.set_skip_notify_controller_on_animation_finished_for_testing(true);
+  animation.Launch();
+
+  // Wait for the ending screenshot to be taken.
+  WaitUntilEndingScreenshotTaken(&animation);
+
+  EXPECT_EQ(0, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kKnock,
+                   HapticTouchpadEffectStrength::kMedium));
+  EXPECT_EQ(0, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kTick,
+                   HapticTouchpadEffectStrength::kMedium));
+
+  // Swipe enough so that our third and fourth desk screenshots are taken, and
+  // then swipe so that the fourth desk is fully shown. There should be 3
+  // visible desk changes in total, which means 3 tick haptic events sent.
+  animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
+  WaitUntilEndingScreenshotTaken(&animation);
+
+  animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
+  WaitUntilEndingScreenshotTaken(&animation);
+
+  animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
+  EXPECT_EQ(3, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kTick,
+                   HapticTouchpadEffectStrength::kMedium));
+
+  // Try doing a full swipe to the right. Test that a knock haptic event is sent
+  // because we are at the edge.
+  animation.UpdateSwipeAnimation(-kTouchpadSwipeLengthForDeskChange);
+  EXPECT_EQ(1, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kKnock,
+                   HapticTouchpadEffectStrength::kMedium));
+
+  // Swipe 3 times to the left. We move from the fourth desk as the visible desk
+  // to the first desk, so there should be three more tick haptic events.
+  animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange);
+  animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange);
+  animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange);
+  EXPECT_EQ(6, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kTick,
+                   HapticTouchpadEffectStrength::kMedium));
+
+  // Swipe to the left while at the first desk. Tests that another haptic event
+  // is sent because we are at the edge.
+  animation.UpdateSwipeAnimation(kTouchpadSwipeLengthForDeskChange);
+  EXPECT_EQ(2, input_controller->GetSendHapticCount(
+                   HapticTouchpadEffect::kKnock,
+                   HapticTouchpadEffectStrength::kMedium));
+}
+
 }  // namespace ash
diff --git a/base/json/json_file_value_serializer.h b/base/json/json_file_value_serializer.h
index 1e1910b..e2479aa 100644
--- a/base/json/json_file_value_serializer.h
+++ b/base/json/json_file_value_serializer.h
@@ -12,6 +12,7 @@
 
 #include "base/base_export.h"
 #include "base/files/file_path.h"
+#include "base/json/json_reader.h"
 #include "base/values.h"
 
 class BASE_EXPORT JSONFileValueSerializer : public base::ValueSerializer {
@@ -54,8 +55,9 @@
 
   // |json_file_path_| is the path of a file that will be source of the
   // deserialization. |options| is a bitmask of JSONParserOptions.
-  explicit JSONFileValueDeserializer(const base::FilePath& json_file_path,
-                                     int options = 0);
+  explicit JSONFileValueDeserializer(
+      const base::FilePath& json_file_path,
+      int options = base::JSON_PARSE_CHROMIUM_EXTENSIONS);
 
   JSONFileValueDeserializer(const JSONFileValueDeserializer&) = delete;
   JSONFileValueDeserializer& operator=(const JSONFileValueDeserializer&) =
diff --git a/base/json/json_parser.cc b/base/json/json_parser.cc
index d5c84aad..fdff416a 100644
--- a/base/json/json_parser.cc
+++ b/base/json/json_parser.cc
@@ -10,6 +10,7 @@
 
 #include "base/check_op.h"
 #include "base/json/json_reader.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/ranges/algorithm.h"
@@ -77,6 +78,20 @@
   return HexStringToInt(input, output);
 }
 
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum class ChromiumJsonExtension {
+  kCComment,
+  kCppComment,
+  kXEscape,
+  kVerticalTabEscape,
+  kControlCharacter,
+  kMaxValue = kControlCharacter,
+};
+
+const char kExtensionHistogramName[] =
+    "Security.JSONParser.ChromiumExtensionUsage";
+
 }  // namespace
 
 // This is U+FFFD.
@@ -327,7 +342,16 @@
   if (!comment_start)
     return false;
 
+  const bool comments_allowed = options_ & JSON_ALLOW_COMMENTS;
+
   if (comment_start == "//") {
+    UmaHistogramEnumeration(kExtensionHistogramName,
+                            ChromiumJsonExtension::kCppComment);
+    if (!comments_allowed) {
+      ReportError(JSON_UNEXPECTED_TOKEN, 0);
+      return false;
+    }
+
     ConsumeChars(2);
     // Single line comment, read to newline.
     while (absl::optional<char> c = PeekChar()) {
@@ -336,6 +360,13 @@
       ConsumeChar();
     }
   } else if (comment_start == "/*") {
+    UmaHistogramEnumeration(kExtensionHistogramName,
+                            ChromiumJsonExtension::kCComment);
+    if (!comments_allowed) {
+      ReportError(JSON_UNEXPECTED_TOKEN, 0);
+      return false;
+    }
+
     ConsumeChars(2);
     char previous_char = '\0';
     // Block comment, read until end marker.
@@ -527,6 +558,19 @@
       return true;
     }
     if (next_char != '\\') {
+      // Per Section 7, "All Unicode characters may be placed within the
+      // quotation marks, except for the characters that MUST be escaped:
+      // quotation mark, reverse solidus, and the control characters (U+0000
+      // through U+001F)".
+      if (next_char <= 0x1F) {
+        UmaHistogramEnumeration(kExtensionHistogramName,
+                                ChromiumJsonExtension::kControlCharacter);
+        if (!(options_ & JSON_ALLOW_CONTROL_CHARS)) {
+          ReportError(JSON_UNSUPPORTED_ENCODING, -1);
+          return false;
+        }
+      }
+
       // If this character is not an escape sequence, track any line breaks and
       // copy next_char to the StringBuilder. The JSON spec forbids unescaped
       // ASCII control characters within a string, including '\r' and '\n', but
@@ -561,6 +605,13 @@
         case 'x': {  // UTF-8 sequence.
           // UTF-8 \x escape sequences are not allowed in the spec, but they
           // are supported here for backwards-compatiblity with the old parser.
+          UmaHistogramEnumeration(kExtensionHistogramName,
+                                  ChromiumJsonExtension::kXEscape);
+          if (!(options_ & JSON_ALLOW_X_ESCAPES)) {
+            ReportError(JSON_INVALID_ESCAPE, -1);
+            return false;
+          }
+
           escape_sequence = ConsumeChars(2);
           if (!escape_sequence) {
             ReportError(JSON_INVALID_ESCAPE, -3);
@@ -612,6 +663,12 @@
           string.Append('\t');
           break;
         case 'v':  // Not listed as valid escape sequence in the RFC.
+          UmaHistogramEnumeration(kExtensionHistogramName,
+                                  ChromiumJsonExtension::kVerticalTabEscape);
+          if (!(options_ & JSON_ALLOW_VERT_TAB)) {
+            ReportError(JSON_INVALID_ESCAPE, -1);
+            return false;
+          }
           string.Append('\v');
           break;
         // All other escape squences are illegal.
diff --git a/base/json/json_parser_unittest.cc b/base/json/json_parser_unittest.cc
index 9c1ea22d..8fbf0a0 100644
--- a/base/json/json_parser_unittest.cc
+++ b/base/json/json_parser_unittest.cc
@@ -277,7 +277,7 @@
   }
 
   {
-    JSONParser parser(JSON_PARSE_RFC);
+    JSONParser parser(JSON_PARSE_RFC | JSON_ALLOW_X_ESCAPES);
     absl::optional<Value> value = parser.Parse("[\"xxx\\xq\"]");
     EXPECT_FALSE(value);
     EXPECT_EQ(JSONParser::FormatErrorMessage(1, 7, JSONParser::kInvalidEscape),
diff --git a/base/json/json_reader.h b/base/json/json_reader.h
index b2821805..0e33c3b 100644
--- a/base/json/json_reader.h
+++ b/base/json/json_reader.h
@@ -23,8 +23,6 @@
 // Configurable (see the JSONParserOptions type) deviations from the RFC:
 // - Allow trailing commas: "[1,2,]".
 // - Replace invalid Unicode with U+FFFD REPLACEMENT CHARACTER.
-//
-// Non-configurable deviations from the RFC:
 // - Allow "// etc\n" and "/* etc */" C-style comments.
 // - Allow ASCII control characters, including literal (not escaped) NUL bytes
 //   and new lines, within a JSON string.
@@ -50,8 +48,7 @@
 namespace base {
 
 enum JSONParserOptions {
-  // Parses the input strictly according to RFC 8259, except for where noted
-  // above.
+  // Parses the input strictly according to RFC 8259.
   JSON_PARSE_RFC = 0,
 
   // Allows commas to exist after the last element in structures.
@@ -62,6 +59,26 @@
   // not set, invalid code points trigger a hard error and parsing
   // fails.
   JSON_REPLACE_INVALID_CHARACTERS = 1 << 1,
+
+  // Allows both C (/* */) and C++ (//) style comments.
+  JSON_ALLOW_COMMENTS = 1 << 2,
+
+  // Permits unescaped ASCII control characters (such as unescaped \r and \n)
+  // in the range [0x00,0x1F].
+  JSON_ALLOW_CONTROL_CHARS = 1 << 3,
+
+  // Permits \\v vertical tab escapes.
+  JSON_ALLOW_VERT_TAB = 1 << 4,
+
+  // Permits \\xNN escapes as described above.
+  JSON_ALLOW_X_ESCAPES = 1 << 5,
+
+  // This parser historically accepted, without configuration flags,
+  // non-standard JSON extensions. This flag enables that traditional parsing
+  // behavior.
+  JSON_PARSE_CHROMIUM_EXTENSIONS = JSON_ALLOW_COMMENTS |
+                                   JSON_ALLOW_CONTROL_CHARS |
+                                   JSON_ALLOW_VERT_TAB | JSON_ALLOW_X_ESCAPES,
 };
 
 class BASE_EXPORT JSONReader {
@@ -94,7 +111,7 @@
   // If |json| is not a properly formed JSON string, returns absl::nullopt.
   static absl::optional<Value> Read(
       StringPiece json,
-      int options = JSON_PARSE_RFC,
+      int options = JSON_PARSE_CHROMIUM_EXTENSIONS,
       size_t max_depth = internal::kAbsoluteMaxDepth);
 
   // Deprecated. Use the Read() method above.
@@ -104,7 +121,7 @@
   // convert to a FooValue at the same time.
   static std::unique_ptr<Value> ReadDeprecated(
       StringPiece json,
-      int options = JSON_PARSE_RFC,
+      int options = JSON_PARSE_CHROMIUM_EXTENSIONS,
       size_t max_depth = internal::kAbsoluteMaxDepth);
 
   // Reads and parses |json| like Read(). Returns a ValueWithError, which on
@@ -112,7 +129,7 @@
   // the error location if appropriate.
   static ValueWithError ReadAndReturnValueWithError(
       StringPiece json,
-      int options = JSON_PARSE_RFC);
+      int options = JSON_PARSE_CHROMIUM_EXTENSIONS);
 };
 
 }  // namespace base
diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc
index 816c8d3..9dbe3b6 100644
--- a/base/json/json_reader_unittest.cc
+++ b/base/json/json_reader_unittest.cc
@@ -377,7 +377,7 @@
 
   absl::optional<Value> root2 = JSONReader::Read(
       "{\"number\":9.87654321, \"null\":null , \"\\x53\" : \"str\", }",
-      JSON_ALLOW_TRAILING_COMMAS);
+      JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS);
   ASSERT_TRUE(root2);
   ASSERT_TRUE(root2->is_dict());
   EXPECT_EQ(*dict_val, *root2);
@@ -389,7 +389,7 @@
       "  \"null\":null,\n"
       "  \"\\x53\":\"str\",\n"
       "}\n",
-      JSON_ALLOW_TRAILING_COMMAS);
+      JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS);
   ASSERT_TRUE(root2);
   ASSERT_TRUE(root2->is_dict());
   EXPECT_EQ(*dict_val, *root2);
@@ -400,7 +400,7 @@
       "  \"null\":null,\r\n"
       "  \"\\x53\":\"str\",\r\n"
       "}\r\n",
-      JSON_ALLOW_TRAILING_COMMAS);
+      JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS);
   ASSERT_TRUE(root2);
   ASSERT_TRUE(root2->is_dict());
   EXPECT_EQ(*dict_val, *root2);
@@ -1014,11 +1014,54 @@
     SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case.input));
 
     JSONReader::ValueWithError root = JSONReader::ReadAndReturnValueWithError(
-        test_case.input, JSON_PARSE_RFC);
+        test_case.input, JSON_PARSE_RFC | JSON_ALLOW_CONTROL_CHARS);
     EXPECT_FALSE(root.value);
     EXPECT_EQ(test_case.error_line, root.error_line);
     EXPECT_EQ(test_case.error_column, root.error_column);
   }
 }
 
+TEST(JSONReaderTest, ChromiumExtensions) {
+  // All of these cases should parse with JSON_PARSE_CHROMIUM_EXTENSIONS but
+  // fail with JSON_PARSE_RFC.
+  const struct {
+    // The JSON input.
+    const char* input;
+    // What JSON_* option permits this extension.
+    int option;
+  } kCases[] = {
+      {"{ /* comment */ \"foo\": 3 }", JSON_ALLOW_COMMENTS},
+      {"{ // comment\n \"foo\": 3 }", JSON_ALLOW_COMMENTS},
+      {"[\"\\xAB\"]", JSON_ALLOW_X_ESCAPES},
+      {"[\"\b\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\f\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\n\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\r\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\t\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\v\"]", JSON_ALLOW_CONTROL_CHARS},
+      {"[\"\\v\"]", JSON_ALLOW_VERT_TAB},
+  };
+
+  for (size_t i = 0; i < base::size(kCases); ++i) {
+    SCOPED_TRACE(testing::Message() << "case " << i);
+    const auto& test_case = kCases[i];
+
+    JSONReader::ValueWithError result = JSONReader::ReadAndReturnValueWithError(
+        test_case.input, JSON_PARSE_RFC);
+    EXPECT_FALSE(result.value);
+
+    result = JSONReader::ReadAndReturnValueWithError(
+        test_case.input, JSON_PARSE_RFC | test_case.option);
+    EXPECT_TRUE(result.value);
+
+    result = JSONReader::ReadAndReturnValueWithError(
+        test_case.input, JSON_PARSE_CHROMIUM_EXTENSIONS);
+    EXPECT_TRUE(result.value);
+
+    result = JSONReader::ReadAndReturnValueWithError(
+        test_case.input, JSON_PARSE_CHROMIUM_EXTENSIONS & ~test_case.option);
+    EXPECT_FALSE(result.value);
+  }
+}
+
 }  // namespace base
diff --git a/base/json/json_string_value_serializer.h b/base/json/json_string_value_serializer.h
index 9faa0ae..27be852 100644
--- a/base/json/json_string_value_serializer.h
+++ b/base/json/json_string_value_serializer.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/base_export.h"
+#include "base/json/json_reader.h"
 #include "base/memory/raw_ptr.h"
 #include "base/strings/string_piece.h"
 #include "base/values.h"
@@ -51,8 +52,9 @@
   // This retains a reference to the contents of |json_string|, so the data
   // must outlive the JSONStringValueDeserializer. |options| is a bitmask of
   // JSONParserOptions.
-  explicit JSONStringValueDeserializer(const base::StringPiece& json_string,
-                                       int options = 0);
+  explicit JSONStringValueDeserializer(
+      const base::StringPiece& json_string,
+      int options = base::JSON_PARSE_CHROMIUM_EXTENSIONS);
 
   JSONStringValueDeserializer(const JSONStringValueDeserializer&) = delete;
   JSONStringValueDeserializer& operator=(const JSONStringValueDeserializer&) =
diff --git a/base/test/values_test_util.cc b/base/test/values_test_util.cc
index cd8be33e..8f82229 100644
--- a/base/test/values_test_util.cc
+++ b/base/test/values_test_util.cc
@@ -219,8 +219,8 @@
 }
 
 Value ParseJson(StringPiece json) {
-  JSONReader::ValueWithError result =
-      JSONReader::ReadAndReturnValueWithError(json, JSON_ALLOW_TRAILING_COMMAS);
+  JSONReader::ValueWithError result = JSONReader::ReadAndReturnValueWithError(
+      json, JSON_PARSE_CHROMIUM_EXTENSIONS | JSON_ALLOW_TRAILING_COMMAS);
   if (!result.value) {
     ADD_FAILURE() << "Failed to parse \"" << json
                   << "\": " << result.error_message;
diff --git a/base/trace_event/trace_event_unittest.cc b/base/trace_event/trace_event_unittest.cc
index 9b6bb44..3712c08 100644
--- a/base/trace_event/trace_event_unittest.cc
+++ b/base/trace_event/trace_event_unittest.cc
@@ -208,8 +208,8 @@
   trace_buffer_.AddFragment(events_str->data());
   trace_buffer_.Finish();
 
-  absl::optional<Value> root =
-      base::JSONReader::Read(json_output_.json_output, JSON_PARSE_RFC);
+  absl::optional<Value> root = base::JSONReader::Read(
+      json_output_.json_output, JSON_PARSE_RFC | JSON_ALLOW_CONTROL_CHARS);
 
   if (!root.has_value()) {
     LOG(ERROR) << json_output_.json_output;
diff --git a/build/config/chromeos/ui_mode.gni b/build/config/chromeos/ui_mode.gni
index aff59af..12d18e8 100644
--- a/build/config/chromeos/ui_mode.gni
+++ b/build/config/chromeos/ui_mode.gni
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/args.gni")
+
 declare_args() {
   # Deprecated, use is_lacros.
   #
@@ -16,15 +18,21 @@
   # is_ash = chromeos_product == "ash"
   chromeos_is_browser_only = false
 
-  # Setting this to true when building LaCrOS-chrome will cause it to
-  # *also* build ash-chrome in a subdirectory using an alternate toolchain.
+  # Setting this to true when building linux Lacros-chrome will cause it to
+  # *also* build linux ash-chrome in a subdirectory using an alternate
+  # toolchain.
   # Don't set this unless you're sure you want it, because it'll double
   # your build time.
   also_build_ash_chrome = false
 
-  # Setting this to true when building ash-chrome will cause it to
-  # *also* build lacros-chrome in a subdirectory using an alternate toolchain.
+  # Setting this to true when building linux ash-chrome will cause it to
+  # *also* build linux Lacros-chrome in a subdirectory using an alternate toolchain.
   also_build_lacros_chrome = false
+
+  # Setting this when building ash-chrome will cause it to
+  # *also* build Lacros-chrome in a subdirectory using an alternate toolchain.
+  # You can set this to either "amd64" or "arm".
+  also_build_lacros_chrome_for_architecture = ""
 }
 
 # is_chromeos_{ash,lacros} is used to specify that it is specific to either
@@ -38,3 +46,15 @@
 
 # also_build_ash_chrome and also_build_lacros_chrome cannot be both true.
 assert(!(also_build_ash_chrome && also_build_lacros_chrome))
+
+# Can't set both also_build_lacros_chrome and
+# also_build_lacros_chrome_for_architecture.
+assert(!(also_build_lacros_chrome == true &&
+             also_build_lacros_chrome_for_architecture != ""))
+
+# also_build_lacros_chrome_for_architecture is for device only.
+assert(is_chromeos_device || also_build_lacros_chrome_for_architecture == "")
+
+# also_build_lacros_chrome_for_architecture is for ash build only.
+assert(!chromeos_is_browser_only ||
+       also_build_lacros_chrome_for_architecture == "")
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 7a1825e..e45fa2c8 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -129,8 +129,8 @@
   # TODO(gbiv): We disable optimizations by default on most platforms because
   # the space overhead is too great. We should use some mixture of profiles and
   # optimization settings to better tune the size increase.
-  thin_lto_enable_optimizations =
-      (is_chromeos_ash || is_android || is_win || is_linux) && is_official_build
+  thin_lto_enable_optimizations = (is_chromeos_ash || is_android || is_win ||
+                                   is_linux || is_mac) && is_official_build
 
   # Initialize all local variables with a pattern. This flag will fill
   # uninitialized floating-point types (and 32-bit pointers) with 0xFF and the
@@ -654,10 +654,11 @@
   if (!is_debug && use_thin_lto && is_a_target_toolchain) {
     assert(use_lld, "LTO is only supported with lld")
 
-    cflags += [
-      "-flto=thin",
-      "-fsplit-lto-unit",
-    ]
+    cflags += [ "-flto=thin" ]
+    if (!is_mac) {
+      # TODO(lgrey): Enable unit splitting for Mac when supported.
+      cflags += [ "-fsplit-lto-unit" ]
+    }
 
     # Limit the size of the ThinLTO cache to the lesser of 10% of
     # available disk space, 40GB and 100000 files.
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index a41df19..3520e428 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -74,7 +74,8 @@
   use_thin_lto =
       is_cfi ||
       (is_clang && is_official_build && chrome_pgo_phase != 1 &&
-       (is_linux || is_win || (is_android && target_os != "chromeos") ||
+       (is_linux || is_win || is_mac ||
+        (is_android && target_os != "chromeos") ||
         ((is_chromeos_ash || is_chromeos_lacros) && is_chromeos_device)))
 
   # If true, use Goma for ThinLTO code generation where applicable.
diff --git a/build/toolchain/cros/BUILD.gn b/build/toolchain/cros/BUILD.gn
index 4cff006..15bdd9c 100644
--- a/build/toolchain/cros/BUILD.gn
+++ b/build/toolchain/cros/BUILD.gn
@@ -174,3 +174,60 @@
     sysroot = cros_v8_snapshot_sysroot
   }
 }
+
+# This toolchain is used when we want to build Lacros using alternate toolchain.
+# To use this, you need to set gn arg 'also_build_lacros_chrome_for_architecture'.
+# See build/config/chromeos/ui_mode.gni
+if (also_build_lacros_chrome_for_architecture != "") {
+  cros_toolchain("lacros_clang") {
+    # These are args for the template.
+    ar = cros_target_ar
+    cc = cros_target_cc
+    cxx = cros_target_cxx
+    ld = cros_target_ld
+
+    if (cros_target_nm != "") {
+      nm = cros_target_nm
+    }
+    if (cros_target_readelf != "") {
+      readelf = cros_target_readelf
+    }
+    extra_cflags = cros_target_extra_cflags
+    extra_cppflags = cros_target_extra_cppflags
+    extra_cxxflags = cros_target_extra_cxxflags
+    extra_ldflags = cros_target_extra_ldflags
+
+    toolchain_args = {
+      if (also_build_lacros_chrome_for_architecture == "amd64") {
+        forward_variables_from(
+            read_file("//build/args/chromeos/amd64-generic-crostoolchain.gni",
+                      "scope"),
+            "*")
+        cros_v8_snapshot_sysroot = "//build/linux/debian_sid_amd64-sysroot"
+      } else if (also_build_lacros_chrome_for_architecture == "arm") {
+        forward_variables_from(
+            read_file("//build/args/chromeos/arm-generic-crostoolchain.gni",
+                      "scope"),
+            "*")
+        cros_v8_snapshot_sysroot = "//build/linux/debian_sid_i386-sysroot"
+      } else {
+        assert(false,
+               "also_build_lacros_chrome_for_architecture is not " +
+                   "one of the supported architecture.")
+      }
+      current_os = "chromeos"
+      target_os = "chromeos"
+      current_cpu = current_cpu
+      also_build_lacros_chrome_for_architecture = ""
+      chromeos_is_browser_only = true
+      use_clang_coverage = false
+      cros_host_sysroot = "//build/linux/debian_sid_amd64-sysroot"
+      cc_wrapper = ""
+      needs_gomacc_path_arg = true
+      clang_use_chrome_plugins = false
+      is_clang = is_clang
+      use_debug_fission = use_debug_fission
+      use_gold = use_gold
+    }
+  }
+}
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 9e2c48f..3a50c88 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -161,6 +161,10 @@
       data_deps += [ "//sandbox/linux:chrome_sandbox" ]
     }
 
+    if (also_build_lacros_chrome_for_architecture != "") {
+      data_deps += [ "//chrome:chrome(//build/toolchain/cros:lacros_clang)" ]
+    }
+
     if (is_win) {
       sources += [
         "app/chrome_exe.rc",
diff --git a/chrome/VERSION b/chrome/VERSION
index cf17d442..aaa50afa 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=99
 MINOR=0
-BUILD=4766
+BUILD=4767
 PATCH=0
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 5cb50a7..220effc 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -449,7 +449,6 @@
   "java/res/drawable-xxxhdpi/unionpay_card.png",
   "java/res/drawable-xxxhdpi/verify_checkmark.png",
   "java/res/drawable/accessibility_tab_switcher_divider.xml",
-  "java/res/drawable/account_picker_background.xml",
   "java/res/drawable/adaptive_toolbar_preference_header.xml",
   "java/res/drawable/arrow_down.xml",
   "java/res/drawable/arrow_up.xml",
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
index 38f5764..ba9546d 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSurfaceMediator.java
@@ -159,14 +159,10 @@
             // blocking the UI thread for several seconds if the accounts cache is not populated
             // yet.
             if (isVisible()) {
-                SigninPromoController.OnDismissListener dismissListener = null;
-                if (ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_SIGNIN_PROMO_DISMISS)) {
-                    dismissListener = this::onDismissPromo;
-                }
                 mSigninPromoController.setUpSyncPromoView(mProfileDataCache,
                         mCoordinator.getSigninPromoView().findViewById(
                                 R.id.signin_promo_view_container),
-                        dismissListener);
+                        this::onDismissPromo);
             }
         }
 
diff --git a/chrome/android/java/res/drawable/account_picker_background.xml b/chrome/android/java/res/drawable/account_picker_background.xml
deleted file mode 100644
index 306316a..0000000
--- a/chrome/android/java/res/drawable/account_picker_background.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2018 The Chromium Authors. All rights reserved.
-     Use of this source code is governed by a BSD-style license that can be
-     found in the LICENSE file. -->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-
-    <solid android:color="@color/default_bg_color_secondary"/>
-    <corners android:radius="8dp"/>
-</shape>
diff --git a/chrome/android/java/res/values/drawables.xml b/chrome/android/java/res/values/drawables.xml
index 90764a09..8569b73 100644
--- a/chrome/android/java/res/values/drawables.xml
+++ b/chrome/android/java/res/values/drawables.xml
@@ -4,6 +4,7 @@
      found in the LICENSE file. -->
 
 <resources>
+    <drawable name="account_picker_background">@drawable/rounded_rectangle_surface_1</drawable>
     <drawable name="ntp_search_box">@drawable/modern_toolbar_text_box_background</drawable>
     <drawable name="badge_update">@drawable/badge_update_dark</drawable>
     <drawable name="ic_error_24dp_filled">@drawable/ic_error_grey800_24dp_filled</drawable>
diff --git a/chrome/android/java/res/values/styles.xml b/chrome/android/java/res/values/styles.xml
index 247fa6f..48045abb 100644
--- a/chrome/android/java/res/values/styles.xml
+++ b/chrome/android/java/res/values/styles.xml
@@ -119,7 +119,7 @@
         TODO(https://crbug.com/819142): Remove textAppearance when all TextViews have text style
         explicitly specified. -->
     <style name="Base.V17.Theme.Chromium.DialogWhenLarge"
-        parent="Theme.MaterialComponents.DayNight.DialogWhenLarge">
+        parent="Theme.BrowserUI.DialogWhenLarge">
         <item name="android:windowBackground">@drawable/bg_white_dialog</item>
         <item name="android:textAppearance">@style/TextAppearance.TextMedium.Primary</item>
         <item name="android:textColorLink">@color/default_text_color_link</item>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
index c4df7d5..6e5f347 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
@@ -49,6 +49,8 @@
 import org.chromium.components.offline_items_collection.LegacyHelpers;
 import org.chromium.components.offline_items_collection.OfflineItem;
 import org.chromium.components.offline_items_collection.OfflineItemState;
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.DataOutputStream;
 import java.io.File;
@@ -942,7 +944,8 @@
             boolean success = false;
             try {
                 URL url = new URL(mOMAInfo.getValue(OMA_INSTALL_NOTIFY_URI));
-                urlConnection = (HttpURLConnection) url.openConnection();
+                urlConnection = (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                        url, NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
                 urlConnection.setDoOutput(true);
                 urlConnection.setUseCaches(false);
                 urlConnection.setRequestMethod("POST");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
index ec92eee..106371d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
@@ -15,6 +15,8 @@
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -103,7 +105,9 @@
             @Override
             protected Integer doInBackground() {
                 try {
-                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+                    HttpURLConnection conn =
+                            (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                                    url, NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
                     conn.setInstanceFollowRedirects(false);
                     conn.setRequestMethod("GET");
                     conn.setDoInput(false);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/net/connectivitydetector/ConnectivityDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/net/connectivitydetector/ConnectivityDetector.java
index df3c554..c77eed5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/net/connectivitydetector/ConnectivityDetector.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/net/connectivitydetector/ConnectivityDetector.java
@@ -23,8 +23,10 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.browser.content.ContentUtils;
+import org.chromium.net.ChromiumNetworkAdapter;
 import org.chromium.net.ConnectionType;
 import org.chromium.net.NetworkChangeNotifier;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -399,7 +401,8 @@
                     Log.i(TAG, "Sending HTTP Probe now to url:" + urlString);
 
                     URL url = new URL(urlString);
-                    urlConnection = (HttpURLConnection) url.openConnection();
+                    urlConnection = (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                            url, NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
                     urlConnection.setInstanceFollowRedirects(false);
                     urlConnection.setRequestMethod(sProbeMethod);
                     urlConnection.setConnectTimeout(timeoutMs);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/measurements/OfflineMeasurementsBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/measurements/OfflineMeasurementsBackgroundTask.java
index 4ec054b..bec07ff4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/measurements/OfflineMeasurementsBackgroundTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/measurements/OfflineMeasurementsBackgroundTask.java
@@ -34,6 +34,8 @@
 import org.chromium.components.background_task_scheduler.TaskIds;
 import org.chromium.components.background_task_scheduler.TaskInfo;
 import org.chromium.components.background_task_scheduler.TaskParameters;
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -504,7 +506,8 @@
                 HttpURLConnection urlConnection = null;
                 try {
                     URL url = new URL(httpProbeUrl);
-                    urlConnection = (HttpURLConnection) url.openConnection();
+                    urlConnection = (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                            url, NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
                     urlConnection.setInstanceFollowRedirects(false);
                     urlConnection.setRequestMethod(httpProbeMethod);
                     urlConnection.setConnectTimeout(httpProbeTimeoutMs);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
index cfbdecb..80b9fcf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
@@ -18,6 +18,8 @@
 import org.chromium.base.StreamUtil;
 import org.chromium.base.ThreadUtils;
 import org.chromium.components.version_info.VersionInfo;
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
@@ -445,7 +447,9 @@
     protected HttpURLConnection createConnection() throws RequestFailureException {
         try {
             URL url = new URL(getRequestGenerator().getServerUrl());
-            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            HttpURLConnection connection =
+                    (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                            url, NetworkTrafficAnnotationTag.MISSING_TRAFFIC_ANNOTATION);
             connection.setConnectTimeout(MS_CONNECTION_TIMEOUT);
             connection.setReadTimeout(MS_CONNECTION_TIMEOUT);
             return connection;
diff --git a/chrome/app/vector_icons/BUILD.gn b/chrome/app/vector_icons/BUILD.gn
index 0d72d572..78f8949 100644
--- a/chrome/app/vector_icons/BUILD.gn
+++ b/chrome/app/vector_icons/BUILD.gn
@@ -251,6 +251,7 @@
       "google_chrome/google_g_logo_monochrome.icon",
       "google_chrome/google_keep_note.icon",
       "google_chrome/google_lens_full_logo.icon",
+      "google_chrome/google_lens_full_logo_dark.icon",
       "google_chrome/google_pay_logo.icon",
       "google_chrome/google_sites.icon",
       "google_chrome/google_super_g.icon",
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3a537f2..118f020 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3994,6 +3994,8 @@
       "metrics/desktop_session_duration/touch_mode_stats_tracker.h",
       "metrics/first_web_contents_profiler.cc",
       "metrics/first_web_contents_profiler.h",
+      "metrics/first_web_contents_profiler_base.cc",
+      "metrics/first_web_contents_profiler_base.h",
       "metrics/incognito_observer_desktop.cc",
       "metrics/power/power_metrics_reporter.cc",
       "metrics/power/power_metrics_reporter.h",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 4b7237e..8332885 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -6375,6 +6375,13 @@
      FEATURE_VALUE_TYPE(page_info::kPageInfoStoreInfo)},
 #endif  // !defined(OS_ANDROID)
 
+#if !defined(OS_ANDROID)
+    {"page-info-history-desktop",
+     flag_descriptions::kPageInfoHistoryDesktopName,
+     flag_descriptions::kPageInfoHistoryDesktopDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(page_info::kPageInfoHistoryDesktop)},
+#endif  // !defined(OS_ANDROID)
+
     {"page-info-about-this-site", flag_descriptions::kPageInfoAboutThisSiteName,
      flag_descriptions::kPageInfoAboutThisSiteDescription,
      kOsDesktop | kOsAndroid,
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index 235efe9..24a0ab0f 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -449,7 +449,7 @@
     const AutocompleteResult& autocomplete_result) {
   JNIEnv* env = AttachCurrentThread();
 
-  autocomplete_controller_->InlineTailPrefixes();
+  autocomplete_controller_->SetTailSuggestContentPrefixes();
 
   // Get the inline-autocomplete text.
   std::u16string inline_autocompletion;
diff --git a/chrome/browser/ash/login/oobe_configuration.cc b/chrome/browser/ash/login/oobe_configuration.cc
index 5f277df..483dbb1 100644
--- a/chrome/browser/ash/login/oobe_configuration.cc
+++ b/chrome/browser/ash/login/oobe_configuration.cc
@@ -84,7 +84,8 @@
 
   base::JSONReader::ValueWithError parsed_json =
       base::JSONReader::ReadAndReturnValueWithError(
-          configuration, base::JSON_ALLOW_TRAILING_COMMAS);
+          configuration, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+                             base::JSON_ALLOW_TRAILING_COMMAS);
   if (!parsed_json.value) {
     LOG(ERROR) << "Error parsing OOBE configuration: "
                << parsed_json.error_message;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 21d897a..bdec672 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -152,6 +152,7 @@
     "//ash/webui/resources:scanning_app_resources_grit",
     "//ash/webui/resources:shimless_rma_resources_grit",
     "//ash/webui/resources:shortcut_customization_app_resources_grit",
+    "//ash/webui/resources:system_extensions_internals_resources_grit",
     "//ash/webui/scanning",
     "//ash/webui/scanning/mojom",
     "//ash/webui/shimless_rma",
diff --git a/chrome/browser/extensions/policy_handlers_unittest.cc b/chrome/browser/extensions/policy_handlers_unittest.cc
index a542285..970c4d2 100644
--- a/chrome/browser/extensions/policy_handlers_unittest.cc
+++ b/chrome/browser/extensions/policy_handlers_unittest.cc
@@ -101,6 +101,9 @@
     "  },"
     "}";
 
+constexpr int kJsonParseOptions =
+    base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS;
+
 TEST(ExtensionListPolicyHandlerTest, CheckPolicySettings) {
   base::ListValue list;
   policy::PolicyMap policy_map;
@@ -156,8 +159,8 @@
   auto url_parses_successfully = [](const char* policy_template,
                                     const std::string& url) {
     std::string policy = base::StringPrintf(policy_template, url.c_str());
-    absl::optional<base::Value> policy_value = base::JSONReader::Read(
-        policy, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+    absl::optional<base::Value> policy_value =
+        base::JSONReader::Read(policy, kJsonParseOptions);
     if (!policy_value)
       return false;
 
@@ -395,9 +398,8 @@
 
 TEST(ExtensionSettingsPolicyHandlerTest, CheckPolicySettings) {
   base::JSONReader::ValueWithError policy_result =
-      base::JSONReader::ReadAndReturnValueWithError(
-          kTestManagementPolicy1,
-          base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+      base::JSONReader::ReadAndReturnValueWithError(kTestManagementPolicy1,
+                                                    kJsonParseOptions);
   ASSERT_TRUE(policy_result.value) << policy_result.error_message;
 
   policy::Schema chrome_schema =
@@ -441,8 +443,8 @@
       base::StringPrintf(policy_template, urls.c_str(), urls.c_str());
 
   std::string error;
-  auto policy_value = base::JSONReader::ReadAndReturnValueWithError(
-      policy, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+  auto policy_value =
+      base::JSONReader::ReadAndReturnValueWithError(policy, kJsonParseOptions);
   policy::Schema chrome_schema =
       policy::Schema::Wrap(policy::GetChromeSchemaData());
   policy::PolicyMap policy_map;
@@ -471,9 +473,8 @@
 TEST(ExtensionSettingsPolicyHandlerTest, ApplyPolicySettings) {
   // Mark as enterprise managed.
   base::JSONReader::ValueWithError policy_result =
-      base::JSONReader::ReadAndReturnValueWithError(
-          kTestManagementPolicy2,
-          base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+      base::JSONReader::ReadAndReturnValueWithError(kTestManagementPolicy2,
+                                                    kJsonParseOptions);
   ASSERT_TRUE(policy_result.value) << policy_result.error_message;
 
   policy::Schema chrome_schema =
@@ -499,15 +500,13 @@
   // the settings apply correctly.
 
   base::JSONReader::ValueWithError policy_result =
-      base::JSONReader::ReadAndReturnValueWithError(
-          kTestManagementPolicy5,
-          base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+      base::JSONReader::ReadAndReturnValueWithError(kTestManagementPolicy5,
+                                                    kJsonParseOptions);
   ASSERT_TRUE(policy_result.value) << policy_result.error_message;
 
   base::JSONReader::ValueWithError stripped_policy_result =
       base::JSONReader::ReadAndReturnValueWithError(
-          kSanitizedTestManagementPolicy5,
-          base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+          kSanitizedTestManagementPolicy5, kJsonParseOptions);
   ASSERT_TRUE(stripped_policy_result.value)
       << stripped_policy_result.error_message;
 
@@ -539,13 +538,11 @@
 TEST(ExtensionSettingsPolicyHandlerTest, NonManagedOffWebstoreExtension) {
   // Mark as not enterprise managed.
   auto policy_result = base::JSONReader::ReadAndReturnValueWithError(
-      kSensitiveTestManagementPolicy,
-      base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+      kSensitiveTestManagementPolicy, kJsonParseOptions);
   ASSERT_TRUE(policy_result.value) << policy_result.error_message;
 
   auto sanitized_policy_result = base::JSONReader::ReadAndReturnValueWithError(
-      kSanitizedTestManagementPolicy,
-      base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
+      kSanitizedTestManagementPolicy, kJsonParseOptions);
   ASSERT_TRUE(sanitized_policy_result.value)
       << sanitized_policy_result.error_message;
 
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index d1a2524..6c834ec 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4352,6 +4352,15 @@
     "expiry_milestone": 97
   },
   {
+    "name": "page-info-history-desktop",
+    "owners": [
+      "eokoyomon",
+      "dullweber",
+      "olesiamarukhno@google.com"
+    ],
+    "expiry_milestone": 102
+  },
+  {
     "name": "page-info-store-info",
     "owners": [ "chrome-shopping@google.com" ],
     "expiry_milestone": 102
@@ -5133,7 +5142,7 @@
   {
     "name": "smart-lock-ui-revamp",
     "owners": [ "cclem", "better-together-dev@google.com" ],
-    "expiry_milestone": 98
+    "expiry_milestone": 101
   },
   {
     "name": "smart-suggestion-for-large-downloads",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 29431d3..27e102d 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1928,6 +1928,10 @@
 const char kPageInfoAboutThisSiteDescription[] =
     "Enable the 'About this site' section in the page info.";
 
+const char kPageInfoHistoryDesktopName[] = "Page info history";
+const char kPageInfoHistoryDesktopDescription[] =
+    "Enable a history section in the page info.";
+
 const char kParallelDownloadingName[] = "Parallel downloading";
 const char kParallelDownloadingDescription[] =
     "Enable parallel downloading to accelerate download speed.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 8fee418..5e7289a 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1103,6 +1103,9 @@
 extern const char kPageInfoAboutThisSiteName[];
 extern const char kPageInfoAboutThisSiteDescription[];
 
+extern const char kPageInfoHistoryDesktopName[];
+extern const char kPageInfoHistoryDesktopDescription[];
+
 extern const char kParallelDownloadingName[];
 extern const char kParallelDownloadingDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index cf6b27d8..0b6cacb 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -139,7 +139,6 @@
     &feed::kFeedImageMemoryCacheSizePercentage,
     &feed::kFeedInteractiveRefresh,
     &feed::kFeedLoadingPlaceholder,
-    &feed::kFeedSignInPromoDismiss,
     &feed::kInterestFeedContentSuggestions,
     &feed::kInterestFeedSpinnerAlwaysAnimate,
     &feed::kInterestFeedV1ClicksAndViewsConditionalUpload,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 417bb18..15ba65d 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -336,7 +336,6 @@
     public static final String FEED_INTERACTIVE_REFRESH = "FeedInteractiveRefresh";
     public static final String FEED_LOADING_PLACEHOLDER = "FeedLoadingPlaceholder";
     public static final String FEED_RELIABILITY_LOGGING = "FeedReliabilityLogging";
-    public static final String FEED_SIGNIN_PROMO_DISMISS = "FeedSignInPromoDismiss";
     public static final String FILLING_PASSWORDS_FROM_ANY_ORIGIN = "FillingPasswordsFromAnyOrigin";
     public static final String FIXED_UMA_SESSION_RESUME_ORDER = "FixedUmaSessionResumeOrder";
     public static final String FOCUS_OMNIBOX_IN_INCOGNITO_TAB_INTENTS =
diff --git a/chrome/browser/metrics/first_web_contents_profiler.cc b/chrome/browser/metrics/first_web_contents_profiler.cc
index e7066db..50bfe5a 100644
--- a/chrome/browser/metrics/first_web_contents_profiler.cc
+++ b/chrome/browser/metrics/first_web_contents_profiler.cc
@@ -14,78 +14,40 @@
 #include "base/memory/memory_pressure_monitor.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
-#include "build/build_config.h"
-#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/metrics/first_web_contents_profiler_base.h"
 #include "chrome/browser/ui/browser_list.h"
-#include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
-#include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
-#include "content/public/browser/web_contents_observer.h"
 
+namespace metrics {
 namespace {
 
-// Reasons for which profiling is deemed complete. Logged in UMA (do not re-
-// order or re-assign).
-enum class FinishReason {
-  // All metrics were successfully gathered.
-  kDone = 0,
-  // Abandon if blocking UI was shown during startup.
-  kAbandonBlockingUI = 1,
-  // Abandon if the WebContents is hidden (lowers scheduling priority).
-  kAbandonContentHidden = 2,
-  // Abandon if the WebContents is destroyed.
-  kAbandonContentDestroyed = 3,
-  // Abandon if the WebContents navigates away from its initial page, as it:
-  //   (1) is no longer a fair timing; and
-  //   (2) can cause http://crbug.com/525209 where the first paint didn't fire
-  //       for the initial content but fires after a lot of idle time when the
-  //       user finally navigates to another page that does trigger it.
-  kAbandonNewNavigation = 4,
-  // Abandon if the WebContents fails to load (e.g. network error, etc.).
-  kAbandonNavigationError = 5,
-  // Abandon if no WebContents was visible at the beginning of startup
-  kAbandonNoInitiallyVisibleContent = 6,
-  kMaxValue = kAbandonNoInitiallyVisibleContent
-};
-
-void RecordFinishReason(FinishReason finish_reason) {
+void RecordFirstWebContentsFinishReason(
+    StartupProfilingFinishReason finish_reason) {
   base::UmaHistogramEnumeration("Startup.FirstWebContents.FinishReason",
                                 finish_reason);
 }
 
-// Note: Instances of this class self destroy when the first non-empty paint
-// happens, or when an event prevents it from being recorded.
-class FirstWebContentsProfiler : public content::WebContentsObserver {
+class FirstWebContentsProfiler : public FirstWebContentsProfilerBase {
  public:
   explicit FirstWebContentsProfiler(content::WebContents* web_contents);
 
   FirstWebContentsProfiler(const FirstWebContentsProfiler&) = delete;
   FirstWebContentsProfiler& operator=(const FirstWebContentsProfiler&) = delete;
 
+ protected:
+  // FirstWebContentsProfilerBase:
+  void RecordFinishReason(StartupProfilingFinishReason finish_reason) override;
+  void RecordNavigationFinished(base::TimeTicks navigation_start) override;
+  void RecordFirstNonEmptyPaint() override;
+  bool WasStartupInterrupted() override;
+
  private:
   ~FirstWebContentsProfiler() override = default;
 
-  // content::WebContentsObserver:
-  void DidStartNavigation(
-      content::NavigationHandle* navigation_handle) override;
-  void DidFinishNavigation(
-      content::NavigationHandle* navigation_handle) override;
-  void DidFirstVisuallyNonEmptyPaint() override;
-  void OnVisibilityChanged(content::Visibility visibility) override;
-  void WebContentsDestroyed() override;
-
-  // Logs |finish_reason| to UMA and deletes this FirstWebContentsProfiler.
-  void FinishedCollectingMetrics(FinishReason finish_reason);
-
-  // Whether a main frame navigation finished since this was created.
-  bool did_finish_first_navigation_ = false;
-
   // Memory pressure listener that will be used to check if memory pressure has
   // an impact on startup.
   base::MemoryPressureListener memory_pressure_listener_;
@@ -93,7 +55,7 @@
 
 FirstWebContentsProfiler::FirstWebContentsProfiler(
     content::WebContents* web_contents)
-    : content::WebContentsObserver(web_contents),
+    : FirstWebContentsProfilerBase(web_contents),
       memory_pressure_listener_(
           FROM_HERE,
           base::BindRepeating(&startup_metric_utils::
@@ -105,119 +67,44 @@
   DCHECK(web_contents->GetController().GetPendingEntry());
 }
 
-void FirstWebContentsProfiler::DidStartNavigation(
-    content::NavigationHandle* navigation_handle) {
-  // The profiler is concerned with the primary main frame navigation only.
-  if (!navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->IsSameDocument()) {
-    return;
-  }
-
-  // FirstWebContentsProfiler is created after DidStartNavigation() has been
-  // dispatched for the first top-level navigation. If another
-  // DidStartNavigation() is received, it means that a new navigation was
-  // initiated.
-  FinishedCollectingMetrics(FinishReason::kAbandonNewNavigation);
+void FirstWebContentsProfiler::RecordFinishReason(
+    StartupProfilingFinishReason finish_reason) {
+  RecordFirstWebContentsFinishReason(finish_reason);
 }
 
-void FirstWebContentsProfiler::DidFinishNavigation(
-    content::NavigationHandle* navigation_handle) {
-  if (startup_metric_utils::WasMainWindowStartupInterrupted()) {
-    FinishedCollectingMetrics(FinishReason::kAbandonBlockingUI);
-    return;
-  }
-
-  // Ignore subframe navigations, pre-rendering, and same-document navigations.
-  if (!navigation_handle->IsInPrimaryMainFrame() ||
-      navigation_handle->IsSameDocument()) {
-    return;
-  }
-
-  if (!navigation_handle->HasCommitted() ||
-      navigation_handle->IsErrorPage()) {
-    FinishedCollectingMetrics(FinishReason::kAbandonNavigationError);
-    return;
-  }
-
-  // It is not possible to get a second top-level DidFinishNavigation() without
-  // first having a DidStartNavigation(), which would have deleted |this|.
-  DCHECK(!did_finish_first_navigation_);
-
-  did_finish_first_navigation_ = true;
-
+void FirstWebContentsProfiler::RecordNavigationFinished(
+    base::TimeTicks navigation_start) {
   startup_metric_utils::RecordFirstWebContentsMainNavigationStart(
-      navigation_handle->NavigationStart());
+      navigation_start);
   startup_metric_utils::RecordFirstWebContentsMainNavigationFinished(
       base::TimeTicks::Now());
 }
 
-void FirstWebContentsProfiler::DidFirstVisuallyNonEmptyPaint() {
-  DCHECK(did_finish_first_navigation_);
-
-  if (startup_metric_utils::WasMainWindowStartupInterrupted()) {
-    FinishedCollectingMetrics(FinishReason::kAbandonBlockingUI);
-    return;
-  }
-
+void FirstWebContentsProfiler::RecordFirstNonEmptyPaint() {
   startup_metric_utils::RecordFirstWebContentsNonEmptyPaint(
       base::TimeTicks::Now(),
       web_contents()->GetMainFrame()->GetProcess()->GetLastInitTime());
-
-  FinishedCollectingMetrics(FinishReason::kDone);
 }
 
-void FirstWebContentsProfiler::OnVisibilityChanged(
-    content::Visibility visibility) {
-  if (visibility != content::Visibility::VISIBLE) {
-    // Stop profiling if the content gets hidden as its load may be
-    // deprioritized and timing it becomes meaningless.
-    FinishedCollectingMetrics(FinishReason::kAbandonContentHidden);
-  }
-}
-
-void FirstWebContentsProfiler::WebContentsDestroyed() {
-  FinishedCollectingMetrics(FinishReason::kAbandonContentDestroyed);
-}
-
-void FirstWebContentsProfiler::FinishedCollectingMetrics(
-    FinishReason finish_reason) {
-  RecordFinishReason(finish_reason);
-  delete this;
+bool FirstWebContentsProfiler::WasStartupInterrupted() {
+  return startup_metric_utils::WasMainWindowStartupInterrupted();
 }
 
 }  // namespace
 
-namespace metrics {
-
 void BeginFirstWebContentsProfiling() {
-  const BrowserList* browser_list = BrowserList::GetInstance();
-
   content::WebContents* visible_contents = nullptr;
+  const BrowserList* browser_list = BrowserList::GetInstance();
   for (Browser* browser : *browser_list) {
-    if (!browser->window()->IsVisible())
-      continue;
-
-    // The active WebContents may be hidden when the window height is small.
-    content::WebContents* contents =
-        browser->tab_strip_model()->GetActiveWebContents();
-
-#if defined(OS_MAC)
-    // TODO(https://crbug.com/1032348): It is incorrect to have a visible
-    // browser window with no active WebContents, but reports on Mac show that
-    // it happens.
-    if (!contents)
-      continue;
-#endif  // defined(OS_MAC)
-
-    if (contents->GetVisibility() != content::Visibility::VISIBLE)
-      continue;
-
-    visible_contents = contents;
-    break;
+    visible_contents =
+        FirstWebContentsProfilerBase::GetVisibleContents(browser);
+    if (visible_contents)
+      break;
   }
 
   if (!visible_contents) {
-    RecordFinishReason(FinishReason::kAbandonNoInitiallyVisibleContent);
+    RecordFirstWebContentsFinishReason(
+        StartupProfilingFinishReason::kAbandonNoInitiallyVisibleContent);
     return;
   }
 
diff --git a/chrome/browser/metrics/first_web_contents_profiler_base.cc b/chrome/browser/metrics/first_web_contents_profiler_base.cc
new file mode 100644
index 0000000..3f2d06b
--- /dev/null
+++ b/chrome/browser/metrics/first_web_contents_profiler_base.cc
@@ -0,0 +1,123 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/first_web_contents_profiler_base.h"
+
+#include "build/build_config.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/navigation_handle.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace metrics {
+
+FirstWebContentsProfilerBase::FirstWebContentsProfilerBase(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+FirstWebContentsProfilerBase::~FirstWebContentsProfilerBase() = default;
+
+// static
+content::WebContents* FirstWebContentsProfilerBase::GetVisibleContents(
+    Browser* browser) {
+  if (!browser->window()->IsVisible())
+    return nullptr;
+
+  // The active WebContents may be hidden when the window height is small.
+  content::WebContents* contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+
+#if defined(OS_MAC)
+  // TODO(https://crbug.com/1032348): It is incorrect to have a visible
+  // browser window with no active WebContents, but reports on Mac show that
+  // it happens.
+  if (!contents)
+    return nullptr;
+#endif  // defined(OS_MAC)
+
+  if (contents->GetVisibility() != content::Visibility::VISIBLE)
+    return nullptr;
+
+  return contents;
+}
+
+void FirstWebContentsProfilerBase::DidStartNavigation(
+    content::NavigationHandle* navigation_handle) {
+  // The profiler is concerned with the primary main frame navigation only.
+  if (!navigation_handle->IsInPrimaryMainFrame() ||
+      navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  // The profiler is created after DidStartNavigation() has been dispatched for
+  // the first top-level navigation. If another DidStartNavigation() is
+  // received, it means that a new navigation was initiated.
+  FinishedCollectingMetrics(
+      StartupProfilingFinishReason::kAbandonNewNavigation);
+}
+
+void FirstWebContentsProfilerBase::DidFinishNavigation(
+    content::NavigationHandle* navigation_handle) {
+  if (WasStartupInterrupted()) {
+    FinishedCollectingMetrics(StartupProfilingFinishReason::kAbandonBlockingUI);
+    return;
+  }
+
+  // Ignore subframe navigations, pre-rendering, and same-document navigations.
+  if (!navigation_handle->IsInPrimaryMainFrame() ||
+      navigation_handle->IsSameDocument()) {
+    return;
+  }
+
+  if (!navigation_handle->HasCommitted() || navigation_handle->IsErrorPage()) {
+    FinishedCollectingMetrics(
+        StartupProfilingFinishReason::kAbandonNavigationError);
+    return;
+  }
+
+  // It is not possible to get a second top-level DidFinishNavigation() without
+  // first having a DidStartNavigation(), which would have deleted |this|.
+  DCHECK(!did_finish_first_navigation_);
+
+  did_finish_first_navigation_ = true;
+
+  RecordNavigationFinished(navigation_handle->NavigationStart());
+}
+
+void FirstWebContentsProfilerBase::DidFirstVisuallyNonEmptyPaint() {
+  DCHECK(did_finish_first_navigation_);
+
+  if (WasStartupInterrupted()) {
+    FinishedCollectingMetrics(StartupProfilingFinishReason::kAbandonBlockingUI);
+    return;
+  }
+
+  RecordFirstNonEmptyPaint();
+  FinishedCollectingMetrics(StartupProfilingFinishReason::kDone);
+}
+
+void FirstWebContentsProfilerBase::OnVisibilityChanged(
+    content::Visibility visibility) {
+  if (visibility != content::Visibility::VISIBLE) {
+    // Stop profiling if the content gets hidden as its load may be
+    // deprioritized and timing it becomes meaningless.
+    FinishedCollectingMetrics(
+        StartupProfilingFinishReason::kAbandonContentHidden);
+  }
+}
+
+void FirstWebContentsProfilerBase::WebContentsDestroyed() {
+  FinishedCollectingMetrics(
+      StartupProfilingFinishReason::kAbandonContentDestroyed);
+}
+
+void FirstWebContentsProfilerBase::FinishedCollectingMetrics(
+    StartupProfilingFinishReason finish_reason) {
+  RecordFinishReason(finish_reason);
+  delete this;
+}
+
+}  // namespace metrics
diff --git a/chrome/browser/metrics/first_web_contents_profiler_base.h b/chrome/browser/metrics/first_web_contents_profiler_base.h
new file mode 100644
index 0000000..92fbd79
--- /dev/null
+++ b/chrome/browser/metrics/first_web_contents_profiler_base.h
@@ -0,0 +1,94 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_
+#define CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_
+
+#include "base/time/time.h"
+#include "content/public/browser/web_contents_observer.h"
+
+namespace content {
+class WebContents;
+}
+
+class Browser;
+
+namespace metrics {
+
+// Reasons for which profiling is deemed complete. Logged in UMA (do not re-
+// order or re-assign).
+enum class StartupProfilingFinishReason {
+  // All metrics were successfully gathered.
+  kDone = 0,
+  // Abandon if blocking UI was shown during startup.
+  kAbandonBlockingUI = 1,
+  // Abandon if the WebContents is hidden (lowers scheduling priority).
+  kAbandonContentHidden = 2,
+  // Abandon if the WebContents is destroyed.
+  kAbandonContentDestroyed = 3,
+  // Abandon if the WebContents navigates away from its initial page, as it:
+  //   (1) is no longer a fair timing; and
+  //   (2) can cause http://crbug.com/525209 where the first paint didn't fire
+  //       for the initial content but fires after a lot of idle time when the
+  //       user finally navigates to another page that does trigger it.
+  kAbandonNewNavigation = 4,
+  // Abandon if the WebContents fails to load (e.g. network error, etc.).
+  kAbandonNavigationError = 5,
+  // Abandon if no WebContents was visible at the beginning of startup
+  kAbandonNoInitiallyVisibleContent = 6,
+  // Abandon if the WebContents was already painted. We set up the profiler too
+  // late and it missed the first non empty paint event.
+  kAbandonAlreadyPaintedContent = 7,
+  kMaxValue = kAbandonAlreadyPaintedContent
+};
+
+// Note: Instances of this class self destroy when the first non-empty paint
+// happens, or when an event prevents it from being recorded.
+class FirstWebContentsProfilerBase : public content::WebContentsObserver {
+ public:
+  FirstWebContentsProfilerBase(const FirstWebContentsProfilerBase&) = delete;
+  FirstWebContentsProfilerBase& operator=(const FirstWebContentsProfilerBase&) =
+      delete;
+
+  // Returns a visible webcontents from `browser` that can be observed for
+  // startup profiling, or `nullptr` if no compatible one was obtained.
+  static content::WebContents* GetVisibleContents(Browser* browser);
+
+ protected:
+  explicit FirstWebContentsProfilerBase(content::WebContents* web_contents);
+
+  // Protected destructor as `FirstWebContentsProfilerBase` deletes itself.
+  ~FirstWebContentsProfilerBase() override;
+
+  // Whether to abort recording metrics if the main window startup was
+  // interrupted. Recording metrics for startups with interruptions pollutes the
+  // collected data, however some flows (e.g. startup on ProfilePicker)
+  // specifically define their metrics to work around the interruptions.
+  virtual bool WasStartupInterrupted() = 0;
+
+  virtual void RecordFinishReason(
+      StartupProfilingFinishReason finish_reason) = 0;
+  virtual void RecordNavigationFinished(base::TimeTicks navigation_start) = 0;
+  virtual void RecordFirstNonEmptyPaint() = 0;
+
+ private:
+  // content::WebContentsObserver:
+  void DidStartNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFinishNavigation(
+      content::NavigationHandle* navigation_handle) override;
+  void DidFirstVisuallyNonEmptyPaint() override;
+  void OnVisibilityChanged(content::Visibility visibility) override;
+  void WebContentsDestroyed() override;
+
+  // Logs |finish_reason| to UMA and deletes this profiler.
+  void FinishedCollectingMetrics(StartupProfilingFinishReason finish_reason);
+
+  // Whether a main frame navigation finished since this was created.
+  bool did_finish_first_navigation_ = false;
+};
+
+}  // namespace metrics
+
+#endif  // CHROME_BROWSER_METRICS_FIRST_WEB_CONTENTS_PROFILER_BASE_H_
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index 1fb2c2cb..310b9a9 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/optimization_guide/prediction/prediction_manager.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
 #include "components/optimization_guide/core/command_line_top_host_provider.h"
 #include "components/optimization_guide/core/hints_processing_util.h"
@@ -69,6 +70,28 @@
                   kOptimizationGuidePredictionModelAndFeaturesStore)));
 }
 
+// Returns the profile to use for when setting up the keyed service when the
+// profile is Off-The-Record. For guest profiles, returns a loaded profile if
+// one exists, otherwise just the original profile of the OTR profile. Note:
+// guest profiles are off-the-record and "original" profiles.
+Profile* GetProfileForOTROptimizationGuide(Profile* profile) {
+  DCHECK(profile);
+  DCHECK(profile->IsOffTheRecord());
+
+  if (profile->IsGuestSession()) {
+    // Guest sessions need to rely on the stores from real profiles
+    // as guest profiles cannot fetch or store new models. Note: only
+    // loaded profiles should be used as we do not want to force load
+    // another profile as that can lead to start up regressions.
+    std::vector<Profile*> profiles =
+        g_browser_process->profile_manager()->GetLoadedProfiles();
+    if (!profiles.empty()) {
+      return profiles[0];
+    }
+  }
+  return profile->GetOriginalProfile();
+}
+
 }  // namespace
 
 // static
@@ -122,7 +145,7 @@
   if (profile->IsOffTheRecord()) {
     OptimizationGuideKeyedService* original_ogks =
         OptimizationGuideKeyedServiceFactory::GetForProfile(
-            profile->GetOriginalProfile());
+            GetProfileForOTROptimizationGuide(profile));
     DCHECK(original_ogks);
     hint_store = original_ogks->GetHintsManager()->hint_store();
     prediction_model_and_features_store =
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
index 6e17165..e4bf849 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
@@ -619,6 +619,23 @@
   run_loop->Run();
 }
 
+#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+// CreateGuestBrowser() is not supported for Android or ChromeOS out of the box.
+IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
+                       GuestProfileUniqueKeyedService) {
+  Browser* guest_browser = CreateGuestBrowser();
+  OptimizationGuideKeyedService* guest_ogks =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(
+          guest_browser->profile());
+  OptimizationGuideKeyedService* ogks =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
+
+  EXPECT_TRUE(guest_ogks);
+  EXPECT_TRUE(ogks);
+  EXPECT_NE(guest_ogks, ogks);
+}
+#endif
+
 class OptimizationGuideKeyedServiceDataSaverUserWithInfobarShownTest
     : public OptimizationGuideKeyedServiceBrowserTest {
  public:
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.cc
index 56ffdea..7b942577 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.cc
@@ -55,7 +55,6 @@
   if (chromeos::ProfileHelper::IsSigninProfile(profile))
     return nullptr;
 #endif
-
   return new OptimizationGuideKeyedService(context);
 }
 
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index 919e2af..9c80c0c7 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -682,6 +682,8 @@
 void PredictionManager::OnStoreInitialized() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   store_is_ready_ = true;
+  LOCAL_HISTOGRAM_BOOLEAN(
+      "OptimizationGuide.PredictionManager.StoreInitialized", true);
 
   // Create the download manager here if we are allowed to.
   if (features::IsModelDownloadingEnabled() && !profile_->IsOffTheRecord() &&
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
index 50e37cf..83c5ea7 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager_browsertest.cc
@@ -814,4 +814,71 @@
   CreateBrowser(profile);
 }
 
+#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
+// CreateGuestBrowser() is not supported for Android or ChromeOS out of the box.
+IN_PROC_BROWSER_TEST_F(PredictionManagerModelDownloadingBrowserTest,
+                       GuestProfileReceivesModel) {
+  SetResponseType(
+      PredictionModelsFetcherRemoteResponseType::kSuccessfulWithValidModelFile);
+
+  {
+    base::HistogramTester histogram_tester;
+    // Register in the primary profile and ensure the model returns.
+    RegisterModelFileObserverWithKeyedService(browser()->profile());
+
+    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+    model_file_observer()->set_model_file_received_callback(base::BindOnce(
+        [](base::RunLoop* run_loop,
+           proto::OptimizationTarget optimization_target,
+           const ModelInfo& model_info) {
+          EXPECT_EQ(optimization_target,
+                    proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+          run_loop->Quit();
+        },
+        run_loop.get()));
+    run_loop->Run();
+    histogram_tester.ExpectUniqueSample(
+        "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus",
+        PredictionModelDownloadStatus::kSuccess, 1);
+  }
+
+  {
+    base::HistogramTester histogram_tester;
+    // Now hook everything up in the guest profile and we should still get the
+    // model back but no additional fetches should be made.
+    Browser* guest_browser = CreateGuestBrowser();
+
+    // To prevent any race, ensure the store has be initialized.
+    RetryForHistogramUntilCountReached(
+        &histogram_tester,
+        "OptimizationGuide.PredictionManager.StoreInitialized", 1);
+    std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+    ModelFileObserver model_file_observer;
+    model_file_observer.set_model_file_received_callback(base::BindOnce(
+        [](base::RunLoop* run_loop,
+           proto::OptimizationTarget optimization_target,
+           const ModelInfo& model_info) {
+          EXPECT_EQ(optimization_target,
+                    proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD);
+          run_loop->Quit();
+        },
+        run_loop.get()));
+    OptimizationGuideKeyedServiceFactory::GetForProfile(
+        guest_browser->profile())
+        ->AddObserverForOptimizationTargetModel(
+            proto::OPTIMIZATION_TARGET_PAINFUL_PAGE_LOAD,
+            /*model_metadata=*/absl::nullopt, &model_file_observer);
+    // Wait until the opt guide is up and the model is loaded as its shared
+    // between profiles.
+    RetryForHistogramUntilCountReached(
+        &histogram_tester,
+        "OptimizationGuide.PredictionModelLoadedVersion.PainfulPageLoad", 1);
+
+    run_loop->Run();
+    histogram_tester.ExpectTotalCount(
+        "OptimizationGuide.PredictionModelDownloadManager.DownloadStatus", 0);
+  }
+}
+#endif
+
 }  // namespace optimization_guide
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.cc b/chrome/browser/password_manager/android/password_store_android_backend.cc
index 903a3c5..5e3221a6 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.cc
+++ b/chrome/browser/password_manager/android/password_store_android_backend.cc
@@ -138,12 +138,6 @@
       metric_infix_(std::move(metric_infix)) {}
 
 PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler(
-    LoginsReply callback,
-    MetricInfix metric_infix)
-    : success_callback_(std::move(callback)),
-      metric_infix_(std::move(metric_infix)) {}
-
-PasswordStoreAndroidBackend::JobReturnHandler::JobReturnHandler(
     PasswordStoreChangeListReply callback,
     MetricInfix metric_infix)
     : success_callback_(std::move(callback)),
@@ -317,13 +311,26 @@
     return;
   }
 
+  LoginsOrErrorReply invoke_noerror_callback = base::BindOnce(
+      [](LoginsReply no_error_callback, LoginsResultOrError result) {
+        // `result` is the LoginsResult returned by `JoinRetrievedLogins`.
+        DCHECK(absl::holds_alternative<LoginsResult>(result));
+        std::move(no_error_callback)
+            .Run(std::move(absl::get<LoginsResult>(result)));
+      },
+      std::move(callback));
+
+  // TODO(https://crbug.com/1229655): Don't use the JobHandler just because it
+  // contains a metrics recorder. Generalize the metrics recording instead!
   LoginsReply record_metrics_and_reply = base::BindOnce(
       [](JobReturnHandler handler, LoginsResult logins) {
+        DCHECK(handler.Holds<LoginsOrErrorReply>());
         handler.RecordMetrics(/*error=*/absl::nullopt);
-        std::move(handler).Get<LoginsReply>().Run(std::move(logins));
+        std::move(handler).Get<LoginsOrErrorReply>().Run(std::move(logins));
       },
-      JobReturnHandler(std::move(callback), JobReturnHandler::MetricInfix(
-                                                "FillMatchingLoginsAsync")));
+      JobReturnHandler(
+          std::move(invoke_noerror_callback),
+          JobReturnHandler::MetricInfix("FillMatchingLoginsAsync")));
 
   auto barrier_callback = base::BarrierCallback<LoginsResult>(
       forms.size(), base::BindOnce(&JoinRetrievedLogins)
diff --git a/chrome/browser/password_manager/android/password_store_android_backend.h b/chrome/browser/password_manager/android/password_store_android_backend.h
index e23d32d0..83af430a 100644
--- a/chrome/browser/password_manager/android/password_store_android_backend.h
+++ b/chrome/browser/password_manager/android/password_store_android_backend.h
@@ -91,7 +91,6 @@
 
     JobReturnHandler();
     JobReturnHandler(LoginsOrErrorReply callback, MetricInfix metric_name);
-    JobReturnHandler(LoginsReply callback, MetricInfix metric_name);
     JobReturnHandler(PasswordStoreChangeListReply callback,
                      MetricInfix metric_infix);
     JobReturnHandler(JobReturnHandler&&);
@@ -119,7 +118,7 @@
     void RecordMetrics(absl::optional<AndroidBackendError> error) const;
 
    private:
-    absl::variant<LoginsReply, LoginsOrErrorReply, PasswordStoreChangeListReply>
+    absl::variant<LoginsOrErrorReply, PasswordStoreChangeListReply>
         success_callback_;
     MetricInfix metric_infix_;
     base::Time start_ = base::Time::Now();
diff --git a/chrome/browser/profiles/profile_window.cc b/chrome/browser/profiles/profile_window.cc
index 58a47365..1bfe710 100644
--- a/chrome/browser/profiles/profile_window.cc
+++ b/chrome/browser/profiles/profile_window.cc
@@ -18,6 +18,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/about_flags.h"
@@ -119,6 +120,8 @@
     chrome::startup::IsFirstRun is_first_run,
     bool always_create) {
   DCHECK(profile);
+  TRACE_EVENT1("browser", "FindOrCreateNewWindowForProfile", "profile_path",
+               profile->GetPath());
 
   if (!always_create) {
     Browser* browser = chrome::FindTabbedBrowser(profile, false);
@@ -144,6 +147,8 @@
                                  Profile* profile,
                                  Profile::CreateStatus status) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  TRACE_EVENT2("browser", "OpenBrowserWindowForProfile", "profile_path",
+               profile->GetPath().AsUTF8Unsafe(), "status", status);
 
   if (status != Profile::CREATE_STATUS_INITIALIZED)
     return;
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox_icon.html b/chrome/browser/resources/new_tab_page/realbox/realbox_icon.html
index f800b04..7226592 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox_icon.html
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox_icon.html
@@ -4,7 +4,6 @@
     display: flex;
     flex-shrink: 0;
     justify-content: center;
-    padding-inline-start: 8px;
     width: 32px;
   }
 
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox_match.html b/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
index 01a43156..f84e2ce 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox_match.html
@@ -30,6 +30,11 @@
     text-overflow: ellipsis;
   }
 
+  #ellipsis {
+    inset-inline-end: 0;
+    position: absolute;
+  }
+
   #focus-indicator {
     background-color: var(--google-blue-600);
     border-radius: 3px;
@@ -44,10 +49,18 @@
     display: block;
   }
 
+  #prefix {
+    opacity: 0;
+  }
+
   #separator {
     white-space: pre;
   }
 
+  #tail-suggest-prefix {
+     position: relative;
+  }
+
   #text-container {
     align-items: center;
     display: flex;
@@ -125,6 +138,12 @@
   <div id="focus-indicator"></div>
   <ntp-realbox-icon id="icon" match="[[match]]"></ntp-realbox-icon>
   <div id="text-container">
+    <span id="tail-suggest-prefix" hidden$="[[!tailSuggestPrefix_]]">
+      <span id="prefix">[[tailSuggestPrefix_]]</span>
+      <!-- This is equivalent to AutocompleteMatch::kEllipsis which is
+           prepended to the match content in other surfaces-->
+      <span id="ellipsis">...&nbsp</span>
+    </span>
     <span id="contents" inner-h-t-m-l="[[contentsHtml_]]"></span>
     <span id="separator" class="dim">[[separatorText_]]</span>
     <span id="description" inner-h-t-m-l="[[descriptionHtml_]]"></span>
diff --git a/chrome/browser/resources/new_tab_page/realbox/realbox_match.js b/chrome/browser/resources/new_tab_page/realbox/realbox_match.js
index a47e6f1..201ed6b 100644
--- a/chrome/browser/resources/new_tab_page/realbox/realbox_match.js
+++ b/chrome/browser/resources/new_tab_page/realbox/realbox_match.js
@@ -87,6 +87,15 @@
       //========================================================================
 
       /**
+       * @type {boolean}
+       * @private
+       */
+      actionIsVisible_: {
+        type: Boolean,
+        computed: `computeActionIsVisible_(match)`,
+      },
+
+      /**
        * Rendered match contents based on autocomplete provided styling.
        * @type {string}
        * @private
@@ -107,15 +116,6 @@
       },
 
       /**
-       * @type {boolean}
-       * @private
-       */
-      actionIsVisible_: {
-        type: Boolean,
-        computed: `computeActionIsVisible_(match)`,
-      },
-
-      /**
        * Remove button's 'aria-label' attribute.
        * @type {string}
        * @private
@@ -143,6 +143,16 @@
         type: String,
         computed: `computeSeparatorText_(match)`,
       },
+
+      /**
+       * Rendered tail suggest common prefix.
+       * @type {string}
+       * @private
+       */
+      tailSuggestPrefix_: {
+        type: String,
+        computed: `computeTailSuggestPrefix_(match)`,
+      },
     };
   }
 
@@ -319,6 +329,23 @@
   }
 
   /**
+   * @return {string}
+   * @private
+   */
+  computeTailSuggestPrefix_() {
+    if (!this.match || !this.match.tailSuggestCommonPrefix) {
+      return '';
+    }
+    const prefix = decodeString16(this.match.tailSuggestCommonPrefix);
+    // Replace last space with non breaking space since spans collapse
+    // trailing white spaces and the prefix always ends with a white space.
+    if (prefix.slice(-1) === ' ') {
+      return prefix.slice(0, -1) + '\u00A0';
+    }
+    return prefix;
+  }
+
+  /**
    * @return {boolean}
    * @private
    */
diff --git a/chrome/browser/resources/tab_search/tab_search_item.ts b/chrome/browser/resources/tab_search/tab_search_item.ts
index 5a30139..e0e3cde 100644
--- a/chrome/browser/resources/tab_search/tab_search_item.ts
+++ b/chrome/browser/resources/tab_search/tab_search_item.ts
@@ -87,21 +87,9 @@
   }
 
   private isOpenTabAndHasMediaAlert_(tabData: TabData): boolean {
-    if (tabData.type != TabItemType.OPEN_TAB ||
-        !(tabData.tab as Tab).alertStates ||
-        (tabData.tab as Tab).alertStates.length == 0) {
-      return false;
-    }
-
-    /* Current UI mocks only have specs for the following media related alert
-     * states. */
-    function validAlertState(alert: TabAlertState): boolean {
-      return alert == TabAlertState.kMediaRecording ||
-          alert == TabAlertState.kAudioPlaying ||
-          alert == TabAlertState.kAudioMuting;
-    }
-
-    return (tabData.tab as Tab).alertStates.some(validAlertState);
+    return tabData.type == TabItemType.OPEN_TAB &&
+        (tabData.tab as Tab).alertStates &&
+        (tabData.tab as Tab).alertStates.length > 0;
   }
 
   /**
diff --git a/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc b/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
index e6ba35f..133d509 100644
--- a/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
+++ b/chrome/browser/safe_browsing/tailored_security/unconsented_message_android.cc
@@ -146,6 +146,7 @@
         ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_MESSAGE_SAFETY_CHECK));
   }
 
+  LogMessageOutcome(TailoredSecurityOutcome::kShown, is_in_flow_);
   messages::MessageDispatcherBridge::Get()->EnqueueMessage(
       message_.get(), web_contents_, messages::MessageScopeType::NAVIGATION,
       messages::MessagePriority::kNormal);
diff --git a/chrome/browser/signin/chrome_signin_client_unittest.cc b/chrome/browser/signin/chrome_signin_client_unittest.cc
index 6286985d..ed2ac0db 100644
--- a/chrome/browser/signin/chrome_signin_client_unittest.cc
+++ b/chrome/browser/signin/chrome_signin_client_unittest.cc
@@ -265,6 +265,8 @@
     case signin_metrics::ProfileSignout::USER_TUNED_OFF_SYNC_FROM_DICE_UI:
       return true;
     case signin_metrics::ProfileSignout::ACCOUNT_REMOVED_FROM_DEVICE:
+    case signin_metrics::ProfileSignout::
+        IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE:
       // TODO(msarda): Add more of the above cases to this "false" branch.
       // For now only ACCOUNT_REMOVED_FROM_DEVICE is here to preserve the status
       // quo. Additional internal sources of sign-out will be moved here in a
@@ -421,6 +423,8 @@
     signin_metrics::ProfileSignout::USER_DELETED_ACCOUNT_COOKIES,
     signin_metrics::ProfileSignout::MOBILE_IDENTITY_CONSISTENCY_ROLLBACK,
     signin_metrics::ProfileSignout::ACCOUNT_ID_MIGRATION,
+    signin_metrics::ProfileSignout::
+        IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE,
 };
 static_assert(base::size(kSignoutSources) ==
                   signin_metrics::ProfileSignout::NUM_PROFILE_SIGNOUT_METRICS,
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_integration_test_mac_win_linux.cc b/chrome/browser/sync/test/integration/two_client_web_apps_integration_test_mac_win_linux.cc
index 6cbc77c..9e1ac38 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_integration_test_mac_win_linux.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_integration_test_mac_win_linux.cc
@@ -18,10 +18,17 @@
 using TwoClientWebAppsIntegrationTestMacWinLinux =
     TwoClientWebAppsIntegrationTestBase;
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -37,10 +44,17 @@
   helper_.CheckLaunchIconShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -55,10 +69,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -71,10 +92,17 @@
   helper_.CheckTabCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -88,10 +116,17 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -107,10 +142,17 @@
   helper_.CheckLaunchIconShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -125,10 +167,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -141,10 +190,17 @@
   helper_.CheckTabCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -158,10 +214,17 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_NavSiteA_InstIconNotShown_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -177,10 +240,17 @@
   helper_.CheckLaunchIconShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromListSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -195,10 +265,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -211,10 +288,17 @@
   helper_.CheckTabCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -228,10 +312,9 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListTabbedSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
+    WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListTabbedSiteA_NavSiteA_InstIconShown_LaunchIconNotShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -246,10 +329,9 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListTabbedSiteA_LaunchFromListSiteA_TabCreated) {
+    WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListTabbedSiteA_LaunchFromListSiteA_TabCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -263,10 +345,9 @@
   helper_.CheckTabCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
+    WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_LaunchFromListSiteA_TabCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -278,10 +359,9 @@
   helper_.CheckTabCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_SwitchProfileClientClient2_InListNotLclyInstSiteC_InstLocallySiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown) {
+    WebAppIntegration_InstCrtShctWindowedSiteC_InListWinSiteC_SwitchProfileClientClient2_InListNotLclyInstSiteC_InstLocallySiteC_InListWinSiteC_NavSiteC_InstIconNotShown_LaunchIconShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -296,10 +376,9 @@
   helper_.CheckLaunchIconShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteC_InListTabbedSiteC_SwitchProfileClientClient2_InListNotLclyInstSiteC_InstLocallySiteC_InListTabbedSiteC_NavSiteC_LaunchIconNotShown) {
+    WebAppIntegration_InstCrtShctTabbedSiteC_InListTabbedSiteC_SwitchProfileClientClient2_InListNotLclyInstSiteC_InstLocallySiteC_InListTabbedSiteC_NavSiteC_LaunchIconNotShown) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -313,10 +392,9 @@
   helper_.CheckLaunchIconNotShown();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstCrtShctWindowedSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
+    WebAppIntegration_TurnSyncOff_InstCrtShctWindowedSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -327,10 +405,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstOmniboxSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
+    WebAppIntegration_TurnSyncOff_InstOmniboxSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -341,10 +418,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstMenuOptionSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
+    WebAppIntegration_TurnSyncOff_InstMenuOptionSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -355,10 +431,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstCrtShctTabbedSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
+    WebAppIntegration_TurnSyncOff_InstCrtShctTabbedSiteA_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -369,10 +444,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstCrtShctWindowedSiteC_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteC) {
+    WebAppIntegration_TurnSyncOff_InstCrtShctWindowedSiteC_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteC) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -383,10 +457,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteC");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_TurnSyncOff_InstCrtShctTabbedSiteC_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteC) {
+    WebAppIntegration_TurnSyncOff_InstCrtShctTabbedSiteC_TurnSyncOn_SwitchProfileClientClient2_InListNotLclyInstSiteC) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -397,10 +470,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteC");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -415,10 +495,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -433,10 +520,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_InstLocallySiteA_InListWinSiteA_LaunchFromIconSiteA_WindowCreated) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -451,10 +545,17 @@
   helper_.CheckWindowCreated();
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -469,10 +570,17 @@
   helper_.CheckAppNotInList("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -487,10 +595,17 @@
   helper_.CheckAppNotInList("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -505,10 +620,9 @@
   helper_.CheckAppNotInList("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
+    WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_UninstallFromListSiteA_NotInListSiteA_SwitchProfileClientClient1_NotInListSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -522,10 +636,17 @@
   helper_.CheckAppNotInList("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -540,10 +661,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -558,10 +686,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -576,10 +711,9 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromListSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -593,10 +727,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstCrtShctWindowedSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -611,10 +752,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstOmniboxSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -629,10 +777,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstMenuOptionSiteA_WindowCreated_InListWinSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
@@ -647,10 +802,17 @@
   helper_.CheckAppInListNotLocallyInstalled("SiteA");
 }
 
-// TODO(crbug.com/1273666): Tests are flaky.
+// TODO(crbug.com/1273666): Test failed on Mac ASAN.
+#if defined(OS_MAC) && defined(ADDRESS_SANITIZER)
+#define MAYBE_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#else
+#define MAYBE_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA \
+  WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA
+#endif
 IN_PROC_BROWSER_TEST_F(
     TwoClientWebAppsIntegrationTestMacWinLinux,
-    DISABLED_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
+    MAYBE_WebAppIntegration_InstCrtShctTabbedSiteA_InListTabbedSiteA_SwitchProfileClientClient2_InListNotLclyInstSiteA_TurnSyncOff_UninstallFromMenuSiteA_TurnSyncOn_InListNotLclyInstSiteA) {
   // Test contents are generated by script. Please do not modify!
   // See `chrome/test/webapps/README.md` for more info.
   // Sheriffs: Disabling this test is supported.
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 188d4a0..dd4a0d5 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2937,6 +2937,7 @@
       "//ash/webui/scanning",
       "//ash/webui/shimless_rma",
       "//ash/webui/shortcut_customization_ui",
+      "//ash/webui/system_extensions_internals_ui",
       "//ash/webui/web_applications",
       "//build:chromeos_buildflags",
       "//chrome/app:generated_resources",
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_controller.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_controller.cc
index 4316dea1d..1638280 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_app_window_shelf_controller.cc
@@ -309,9 +309,6 @@
     aura::Window* window) {
   DCHECK(observed_windows_.IsObservingSource(window));
   observed_windows_.RemoveObservation(window);
-  if (IgnoreWindow(window)) {
-    return;
-  }
 
   if (arc_tracker_)
     arc_tracker_->RemoveCandidateWindow(window);
diff --git a/chrome/browser/ui/startup/startup_browser_creator_impl.cc b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
index c8421f45..bfd2cf06 100644
--- a/chrome/browser/ui/startup/startup_browser_creator_impl.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator_impl.cc
@@ -513,10 +513,6 @@
         StartupTabs new_features_tabs;
         new_features_tabs = provider.GetNewFeaturesTabs(whats_new_enabled);
         AppendTabs(new_features_tabs, &tabs);
-      } else {
-        // Record the current version so that What's New will not be shown until
-        // after the next major version update.
-        whats_new::SetLastVersion(g_browser_process->local_state());
       }
     }
 
diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.cc b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
index 5cfd41b..7318216 100644
--- a/chrome/browser/ui/toolbar/back_forward_menu_model.cc
+++ b/chrome/browser/ui/toolbar/back_forward_menu_model.cc
@@ -292,6 +292,8 @@
 
 int BackForwardMenuModel::GetHistoryItemCount() const {
   WebContents* contents = GetWebContents();
+  if (!contents)
+    return 0;
 
   int items = contents->GetController().GetCurrentEntryIndex();
   if (model_type_ == ModelType::kForward) {
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
index 6bac676..875a4f7 100644
--- a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.cc
@@ -52,7 +52,7 @@
 
 // Constant for DropdownButton
 const int kDropdownButtonIconSize = 15;
-const int kDropdownButtonBackgroundRadius = 15;
+const int kDropdownButtonBackgroundRadius = 10;
 constexpr gfx::Insets kDropdownButtonBorderInsets{4};
 
 // The maximum number of audio devices to count when recording the
@@ -168,7 +168,6 @@
       vector_icons::kCaretUpIcon, kDropdownButtonIconSize, foreground_color);
   SetToggledImage(views::Button::STATE_NORMAL, &caret_down_image);
   views::InkDrop::Get(this)->SetBaseColor(foreground_color);
-  SetHighlighted(true);
 }
 
 MediaItemUIDeviceSelectorView::MediaItemUIDeviceSelectorView(
@@ -508,12 +507,6 @@
   return is_expanded_;
 }
 
-bool MediaItemUIDeviceSelectorView::OnMousePressed(
-    const ui::MouseEvent& event) {
-  // Stop the mouse click event from bubbling to parent views.
-  return true;
-}
-
 void MediaItemUIDeviceSelectorView::AddObserver(
     MediaItemUIDeviceSelectorObserver* observer) {
   observers_.AddObserver(observer);
diff --git a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
index b6b609ab..384ed0e4 100644
--- a/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_item_ui_device_selector_view.h
@@ -87,9 +87,6 @@
   void OnDropdownButtonClicked() override;
   bool IsDeviceSelectorExpanded() override;
 
-  // views::View
-  bool OnMousePressed(const ui::MouseEvent& event) override;
-
   void AddObserver(MediaItemUIDeviceSelectorObserver* observer);
 
   views::Label* GetExpandDeviceSelectorLabelForTesting();
diff --git a/chrome/browser/ui/views/lens/lens_side_panel_controller.cc b/chrome/browser/ui/views/lens/lens_side_panel_controller.cc
index ca028a1..2f2473d 100644
--- a/chrome/browser/ui/views/lens/lens_side_panel_controller.cc
+++ b/chrome/browser/ui/views/lens/lens_side_panel_controller.cc
@@ -72,6 +72,7 @@
         base::UserMetricsAction("LensSidePanel.HideChromeSidePanel"));
     browser_view_->toolbar()->side_panel_button()->HideSidePanel();
   }
+
   side_panel_view_->GetWebContents()->GetController().LoadURLWithParams(
       content::NavigationController::LoadURLParams(params));
   if (side_panel_->GetVisible()) {
@@ -150,4 +151,12 @@
   Close();
 }
 
+void LensSidePanelController::LoadProgressChanged(double progress) {
+  if(progress == 1.0) {
+    side_panel_view_->SetContentVisible(true);
+  } else {
+    side_panel_view_->SetContentVisible(false);
+  }
+}
+
 }  // namespace lens
diff --git a/chrome/browser/ui/views/lens/lens_side_panel_controller.h b/chrome/browser/ui/views/lens/lens_side_panel_controller.h
index 9fa3f76..07e97af4 100644
--- a/chrome/browser/ui/views/lens/lens_side_panel_controller.h
+++ b/chrome/browser/ui/views/lens/lens_side_panel_controller.h
@@ -31,6 +31,8 @@
   LensSidePanelController& operator=(const LensSidePanelController&) = delete;
   ~LensSidePanelController() override;
 
+  void LoadProgressChanged(double progress) override;
+
   // Opens the Lens side panel with the given Lens results URL.
   void OpenWithURL(const content::OpenURLParams& params);
 
diff --git a/chrome/browser/ui/views/lens/lens_side_panel_view.cc b/chrome/browser/ui/views/lens/lens_side_panel_view.cc
index 44fe73c..ef25663b 100644
--- a/chrome/browser/ui/views/lens/lens_side_panel_view.cc
+++ b/chrome/browser/ui/views/lens/lens_side_panel_view.cc
@@ -76,6 +76,13 @@
 constexpr int kDefaultSidePanelHeaderHeight = 40;
 constexpr int kGoogleLensLogoWidth = 87;
 constexpr int kGoogleLensLogoHeight = 16;
+const char kStaticGhostCardDataURL[] =
+    "data:text/html;charset=utf-8,"
+    "<!DOCTYPE html>"
+    "<style>"
+    "html, body {"
+    "background-image: url('https://www.gstatic.com/lens/web/ui/side_panel_loading.gif');"
+    "}</style>";
 
 LensSidePanelView::LensSidePanelView(content::BrowserContext* browser_context,
                                      base::RepeatingClosure close_callback,
@@ -87,7 +94,12 @@
   SetCrossAxisAlignment(views::LayoutAlignment::kStretch);
   CreateAndInstallHeader(close_callback, launch_callback);
   separator_ = AddChildView(std::make_unique<views::Separator>());
+  loading_indicator_web_view_ = AddChildView(CreateWebView(this, browser_context));
+  loading_indicator_web_view_->GetWebContents()->GetController().LoadURL(
+        GURL(kStaticGhostCardDataURL), content::Referrer(), ui::PAGE_TRANSITION_FROM_API,
+        std::string());
   web_view_ = AddChildView(CreateWebView(this, browser_context));
+  web_view_->SetVisible(false);
 }
 
 content::WebContents* LensSidePanelView::GetWebContents() {
@@ -99,13 +111,16 @@
   const auto* color_provider = GetColorProvider();
   separator_->SetColor(color_provider->GetColor(ui::kColorMenuSeparator));
 
-  const SkColor color = color_provider->GetColor(ui::kColorIcon);
   // kGoogleLensFullLogoIcon is rectangular. We should create a tiled image so
   // that the coordinates and scale are correct. The vector icon should have its
-  // own fill color.
-  gfx::ImageSkia image = gfx::ImageSkiaOperations::CreateTiledImage(
-      gfx::CreateVectorIcon(kGoogleLensFullLogoIcon, color), 0, 0,
-      kGoogleLensLogoWidth, kGoogleLensLogoHeight);
+  // own fill color. The same applies to the dark mode icon.
+  const SkColor color = color_provider->GetColor(ui::kColorIcon);
+  const gfx::VectorIcon& icon = GetNativeTheme()->ShouldUseDarkColors()
+                                    ? kGoogleLensFullLogoDarkIcon
+                                    : kGoogleLensFullLogoIcon;
+  const gfx::ImageSkia image = gfx::ImageSkiaOperations::CreateTiledImage(
+      gfx::CreateVectorIcon(icon, color), 0, 0, kGoogleLensLogoWidth,
+      kGoogleLensLogoHeight);
   branding_->SetImage(image);
 }
 
@@ -162,6 +177,11 @@
   AddChildView(std::move(header));
 }
 
+void LensSidePanelView::SetContentVisible(bool visible) {
+  web_view_->SetVisible(visible);
+  loading_indicator_web_view_->SetVisible(!visible);
+}
+
 LensSidePanelView::~LensSidePanelView() = default;
 
 }  // namespace lens
diff --git a/chrome/browser/ui/views/lens/lens_side_panel_view.h b/chrome/browser/ui/views/lens/lens_side_panel_view.h
index 501669e..8f3e42d 100644
--- a/chrome/browser/ui/views/lens/lens_side_panel_view.h
+++ b/chrome/browser/ui/views/lens/lens_side_panel_view.h
@@ -36,12 +36,15 @@
   // views::FlexLayoutView:
   void OnThemeChanged() override;
 
+  void SetContentVisible(bool visible);
+
  private:
   void CreateAndInstallHeader(base::RepeatingClosure close_callback,
                               base::RepeatingClosure launch_callback);
 
   raw_ptr<views::ImageView> branding_;
   raw_ptr<views::Separator> separator_;
+  raw_ptr<views::WebView> loading_indicator_web_view_;
   raw_ptr<views::WebView> web_view_;
   raw_ptr<views::ImageButton> close_button_;
   raw_ptr<views::ImageButton> launch_button_;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
index 5205716e..104c5239 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view.cc
@@ -358,7 +358,7 @@
   }
 
   // Fix-up any matches due to tail suggestions, before display below.
-  edit_model_->autocomplete_controller()->InlineTailPrefixes();
+  edit_model_->autocomplete_controller()->SetTailSuggestContentPrefixes();
 
   // Update the match cached by each row, in the process of doing so make sure
   // we have enough row views.
diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.cc b/chrome/browser/ui/views/payments/payment_request_row_view.cc
index 0ff0972..84d9c78 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.cc
@@ -14,7 +14,10 @@
 #include "ui/color/color_provider.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
+#include "ui/views/controls/focus_ring.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/layout/table_layout.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
 
@@ -168,6 +171,11 @@
     SchedulePaint();
   }
   View::OnFocus();
+  views::FocusRing* focus_ring = views::FocusRing::Get(this);
+  views::TableLayout* layout =
+      static_cast<views::TableLayout*>(GetLayoutManager());
+  if (focus_ring && layout)
+    layout->SetChildViewIgnoredByLayout(focus_ring, true);
 }
 
 void PaymentRequestRowView::OnBlur() {
diff --git a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
index 688f330..f49e84d 100644
--- a/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
@@ -59,8 +59,9 @@
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/layout/grid_layout.h"
+#include "ui/views/layout/box_layout_view.h"
+#include "ui/views/layout/table_layout.h"
+#include "ui/views/layout/table_layout_view.h"
 #include "ui/views/view.h"
 
 namespace payments {
@@ -132,8 +133,33 @@
     std::unique_ptr<views::View> trailing_button,
     bool clickable,
     bool extra_trailing_inset,
-    views::GridLayout::Alignment vertical_alignment =
-        views::GridLayout::LEADING) {
+    views::LayoutAlignment vertical_alignment =
+        views::LayoutAlignment::kStart) {
+  constexpr int kNameColumnWidth = 112;
+  constexpr int kPaddingAfterName = 32;
+  constexpr int kPaddingColumnsWidth = 25;
+
+  auto table_layout = std::make_unique<views::TableLayout>();
+  table_layout
+      // A column for the section name.
+      ->AddColumn(views::LayoutAlignment::kStart, vertical_alignment,
+                  views::TableLayout::kFixedSize,
+                  views::TableLayout::ColumnSize::kFixed, kNameColumnWidth, 0)
+      .AddPaddingColumn(views::TableLayout::kFixedSize, kPaddingAfterName)
+      // A column for the content.
+      .AddColumn(views::LayoutAlignment::kStretch, vertical_alignment, 1.0,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      // A column for the extra content.
+      .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kCenter,
+                 views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddPaddingColumn(views::TableLayout::kFixedSize, kPaddingColumnsWidth)
+      // A column for the trailing_button.
+      .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kCenter,
+                 views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddRows(1, views::TableLayout::kFixedSize);
+
   const int trailing_inset = extra_trailing_inset
                                  ? kPaymentRequestRowHorizontalInsets +
                                        kPaymentRequestRowExtraRightInset
@@ -141,66 +167,28 @@
   const gfx::Insets row_insets(
       kPaymentRequestRowVerticalInsets, kPaymentRequestRowHorizontalInsets,
       kPaymentRequestRowVerticalInsets, trailing_inset);
-  std::unique_ptr<PaymentRequestRowView> row =
-      std::make_unique<PaymentRequestRowView>(std::move(callback), clickable,
-                                              row_insets);
-  views::GridLayout* layout =
-      row->SetLayoutManager(std::make_unique<views::GridLayout>());
 
-  views::ColumnSet* columns = layout->AddColumnSet(0);
-  // A column for the section name.
-  constexpr int kNameColumnWidth = 112;
-  columns->AddColumn(views::GridLayout::LEADING, vertical_alignment,
-                     views::GridLayout::kFixedSize,
-                     views::GridLayout::ColumnSize::kFixed, kNameColumnWidth,
-                     0);
-
-  constexpr int kPaddingAfterName = 32;
-  columns->AddPaddingColumn(views::GridLayout::kFixedSize, kPaddingAfterName);
-
-  // A column for the content.
-  columns->AddColumn(views::GridLayout::FILL, vertical_alignment, 1.0,
-                     views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-  // A column for the extra content.
-  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
-                     views::GridLayout::kFixedSize,
-                     views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-
-  constexpr int kPaddingColumnsWidth = 25;
-  columns->AddPaddingColumn(views::GridLayout::kFixedSize,
-                            kPaddingColumnsWidth);
-  // A column for the trailing_button.
-  columns->AddColumn(views::GridLayout::TRAILING, views::GridLayout::CENTER,
-                     views::GridLayout::kFixedSize,
-                     views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-
-  layout->StartRow(views::GridLayout::kFixedSize, 0);
-  std::unique_ptr<views::Label> name_label = CreateMediumLabel(section_name);
-  name_label->SetMultiLine(true);
-  name_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  layout->AddView(std::move(name_label));
-
-  if (content_view) {
-    content_view->SetCanProcessEventsWithinSubtree(false);
-    layout->AddView(std::move(content_view));
-  } else {
-    layout->SkipColumns(1);
-  }
-
-  if (extra_content_view) {
-    extra_content_view->SetCanProcessEventsWithinSubtree(false);
-    layout->AddView(std::move(extra_content_view));
-  } else {
-    layout->SkipColumns(1);
-  }
-
-  layout->AddView(std::move(trailing_button));
-
-  row->SetAccessibleName(
-      l10n_util::GetStringFUTF16(IDS_PAYMENTS_ROW_ACCESSIBLE_NAME_FORMAT,
-                                 section_name, accessible_content));
-
-  return row;
+  return views::Builder<PaymentRequestRowView>()
+      .SetLayoutManager(std::move(table_layout))
+      .SetCallback(std::move(callback))
+      .SetClickable(clickable)
+      .SetRowInsets(row_insets)
+      .SetAccessibleName(
+          l10n_util::GetStringFUTF16(IDS_PAYMENTS_ROW_ACCESSIBLE_NAME_FORMAT,
+                                     section_name, accessible_content))
+      .AddChildren(
+          views::Builder<views::Label>(CreateMediumLabel(section_name))
+              .SetMultiLine(true)
+              .SetHorizontalAlignment(gfx::ALIGN_LEFT),
+          content_view ? views::Builder<views::View>(std::move(content_view))
+                             .SetCanProcessEventsWithinSubtree(false)
+                       : views::Builder<views::View>(),
+          extra_content_view
+              ? views::Builder<views::View>(std::move(extra_content_view))
+                    .SetCanProcessEventsWithinSubtree(false)
+              : views::Builder<views::View>(),
+          views::Builder<views::View>(std::move(trailing_button)))
+      .Build();
 }
 
 std::unique_ptr<views::View> CreateInlineCurrencyAmountItem(
@@ -208,39 +196,26 @@
     const std::u16string& amount,
     bool hint_color,
     bool bold) {
-  std::unique_ptr<views::View> item_amount_line =
-      std::make_unique<views::View>();
-  views::GridLayout* item_amount_layout =
-      item_amount_line->SetLayoutManager(std::make_unique<views::GridLayout>());
-  views::ColumnSet* item_amount_columns = item_amount_layout->AddColumnSet(0);
-  item_amount_columns->AddColumn(
-      views::GridLayout::LEADING, views::GridLayout::LEADING,
-      views::GridLayout::kFixedSize,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-  item_amount_columns->AddColumn(
-      views::GridLayout::TRAILING, views::GridLayout::LEADING, 1.0,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-
   DCHECK(!bold || !hint_color);
-  std::unique_ptr<views::Label> currency_label;
-  if (bold)
-    currency_label = CreateBoldLabel(currency);
-  else if (hint_color)
-    currency_label = CreateHintLabel(currency);
-  else
-    currency_label = std::make_unique<views::Label>(currency);
-
-  std::unique_ptr<views::Label> amount_label =
-      bold ? CreateBoldLabel(amount) : std::make_unique<views::Label>(amount);
-  amount_label->SetMultiLine(true);
-  amount_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  amount_label->SetAllowCharacterBreak(true);
-
-  item_amount_layout->StartRow(views::GridLayout::kFixedSize, 0);
-  item_amount_layout->AddView(std::move(currency_label));
-  item_amount_layout->AddView(std::move(amount_label));
-
-  return item_amount_line;
+  return views::Builder<views::TableLayoutView>()
+      .AddColumn(views::LayoutAlignment::kStart, views::LayoutAlignment::kStart,
+                 views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kStart,
+                 1.0, views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      .AddRows(1, views::TableLayout::kFixedSize, 0)
+      .AddChildren((bold ? views::Builder<views::Label>(CreateBoldLabel(u""))
+                         : (hint_color ? views::Builder<views::Label>(
+                                             CreateHintLabel(u""))
+                                       : views::Builder<views::Label>()))
+                       .SetText(currency),
+                   (bold ? views::Builder<views::Label>(CreateBoldLabel(u""))
+                         : views::Builder<views::Label>())
+                       .SetText(amount)
+                       .SetMultiLine(true)
+                       .SetHorizontalAlignment(gfx::ALIGN_LEFT)
+                       .SetAllowCharacterBreak(true))
+      .Build();
 }
 
 // A class used to build Payment Sheet Rows. Construct an instance of it, chain
@@ -351,7 +326,7 @@
         views::Button::PressedCallback(), section_name_, accessible_content_,
         std::move(content_view), nullptr, std::move(button),
         /*clickable=*/false,
-        /*extra_trailing_inset=*/false, views::GridLayout::CENTER);
+        /*extra_trailing_inset=*/false, views::LayoutAlignment::kCenter);
   }
 
   views::Button::PressedCallback GetPressedCallback() const {
@@ -424,38 +399,32 @@
   if (!spec())
     return;
 
-  views::GridLayout* layout =
-      content_view->SetLayoutManager(std::make_unique<views::GridLayout>());
-  views::ColumnSet* columns = layout->AddColumnSet(0);
-  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER, 1.0,
-                     views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
+  auto builder = views::Builder<views::View>(content_view)
+                     .SetLayoutManager(std::make_unique<views::BoxLayout>(
+                         views::BoxLayout::Orientation::kVertical));
 
   if (!spec()->retry_error_message().empty()) {
-    std::unique_ptr<views::View> warning_view =
-        CreateWarningView(spec()->retry_error_message(), true /* show_icon */);
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    layout->AddView(std::move(warning_view));
+    builder.AddChild(views::Builder<views::View>(CreateWarningView(
+        spec()->retry_error_message(), true /* show_icon */)));
   }
 
   // The shipping address and contact info rows are optional.
   std::unique_ptr<PaymentRequestRowView> summary_row =
       CreatePaymentSheetSummaryRow();
   if (!summary_row)
-    return;
+    return std::move(builder).BuildChildren();
 
   PaymentRequestRowView* previous_row = summary_row.get();
-  layout->StartRow(views::GridLayout::kFixedSize, 0);
-  layout->AddView(std::move(summary_row));
+  builder.AddChild(views::Builder<views::View>(std::move(summary_row)));
 
   if (state()->ShouldShowShippingSection()) {
     std::unique_ptr<PaymentRequestRowView> shipping_row = CreateShippingRow();
     if (!shipping_row)
-      return;
+      return std::move(builder).BuildChildren();
 
     shipping_row->set_previous_row(previous_row->AsWeakPtr());
     previous_row = shipping_row.get();
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    layout->AddView(std::move(shipping_row));
+    builder.AddChild(views::Builder<views::View>(std::move(shipping_row)));
     // It's possible for requestShipping to be true and for there to be no
     // shipping options yet (they will come in updateWith).
     // TODO(crbug.com/707353): Put a better placeholder row, instead of no row.
@@ -464,26 +433,25 @@
     if (shipping_option_row) {
       shipping_option_row->set_previous_row(previous_row->AsWeakPtr());
       previous_row = shipping_option_row.get();
-      layout->StartRow(views::GridLayout::kFixedSize, 0);
-      layout->AddView(std::move(shipping_option_row));
+      builder.AddChild(
+          views::Builder<views::View>(std::move(shipping_option_row)));
     }
   }
   std::unique_ptr<PaymentRequestRowView> payment_method_row =
       CreatePaymentMethodRow();
   payment_method_row->set_previous_row(previous_row->AsWeakPtr());
   previous_row = payment_method_row.get();
-  layout->StartRow(views::GridLayout::kFixedSize, 0);
-  layout->AddView(std::move(payment_method_row));
+  builder.AddChild(views::Builder<views::View>(std::move(payment_method_row)));
   if (state()->ShouldShowContactSection()) {
     std::unique_ptr<PaymentRequestRowView> contact_info_row =
         CreateContactInfoRow();
     contact_info_row->set_previous_row(previous_row->AsWeakPtr());
     previous_row = contact_info_row.get();
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    layout->AddView(std::move(contact_info_row));
+    builder.AddChild(views::Builder<views::View>(std::move(contact_info_row)));
   }
-  layout->StartRow(views::GridLayout::kFixedSize, 0);
-  layout->AddView(CreateDataSourceRow());
+  builder.AddChild(views::Builder<views::View>(CreateDataSourceRow()));
+
+  std::move(builder).BuildChildren();
 }
 
 // Adds the product logo to the footer.
@@ -509,16 +477,16 @@
   if (!spec())
     return nullptr;
 
-  std::unique_ptr<views::View> inline_summary = std::make_unique<views::View>();
-  views::GridLayout* layout =
-      inline_summary->SetLayoutManager(std::make_unique<views::GridLayout>());
-  views::ColumnSet* columns = layout->AddColumnSet(0);
-  columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING,
-                     1.0, views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
   constexpr int kItemSummaryPriceFixedWidth = 96;
-  columns->AddColumn(views::GridLayout::FILL, views::GridLayout::LEADING,
-                     views::GridLayout::kFixedSize,
-                     views::GridLayout::ColumnSize::kFixed,
+  auto view_builder =
+      views::Builder<views::TableLayoutView>()
+          .AddColumn(views::LayoutAlignment::kStart,
+                     views::LayoutAlignment::kStart, 1.0,
+                     views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+          .AddColumn(views::LayoutAlignment::kStretch,
+                     views::LayoutAlignment::kStart,
+                     views::TableLayout::kFixedSize,
+                     views::TableLayout::ColumnSize::kFixed,
                      kItemSummaryPriceFixedWidth, kItemSummaryPriceFixedWidth);
 
   const std::vector<const mojom::PaymentItemPtr*>& items =
@@ -536,48 +504,49 @@
                                ? items.size()
                                : kMaxNumberOfItemsShown;
   for (size_t i = 0; i < items.size() && i < displayed_items; ++i) {
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    std::unique_ptr<views::Label> summary =
-        std::make_unique<views::Label>(base::UTF8ToUTF16((*items[i])->label));
-    summary->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-    layout->AddView(std::move(summary));
-
-    layout->AddView(CreateInlineCurrencyAmountItem(
-        is_mixed_currency ? base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode(
-                                (*items[i])->amount))
-                          : std::u16string(),
-        spec()->GetFormattedCurrencyAmount((*items[i])->amount), true, false));
+    view_builder.AddRows(1, views::TableLayout::kFixedSize, 0)
+        .AddChildren(
+            views::Builder<views::Label>()
+                .SetText(base::UTF8ToUTF16((*items[i])->label))
+                .SetHorizontalAlignment(gfx::ALIGN_LEFT),
+            views::Builder<views::View>(CreateInlineCurrencyAmountItem(
+                is_mixed_currency
+                    ? base::UTF8ToUTF16(
+                          spec()->GetFormattedCurrencyCode((*items[i])->amount))
+                    : std::u16string(),
+                spec()->GetFormattedCurrencyAmount((*items[i])->amount), true,
+                false)));
   }
 
   size_t hidden_item_count = items.size() - displayed_items;
   if (hidden_item_count > 0) {
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    std::unique_ptr<views::Label> label =
-        CreateHintLabel(l10n_util::GetPluralStringFUTF16(
-            IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS, hidden_item_count));
-    layout->AddView(std::move(label));
-    if (is_mixed_currency) {
-      std::unique_ptr<views::Label> multiple_currency_label =
-          CreateHintLabel(l10n_util::GetStringUTF16(
-              IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR));
-      layout->AddView(std::move(multiple_currency_label));
-    }
+    view_builder.AddRows(1, views::TableLayout::kFixedSize, 0)
+        .AddChildren(
+            views::Builder<views::Label>(
+                CreateHintLabel(l10n_util::GetPluralStringFUTF16(
+                    IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MORE_ITEMS,
+                    hidden_item_count))),
+            is_mixed_currency
+                ? views::Builder<
+                      views::View>(CreateHintLabel(l10n_util::GetStringUTF16(
+                      IDS_PAYMENT_REQUEST_ORDER_SUMMARY_MULTIPLE_CURRENCY_INDICATOR)))
+                : views::Builder<views::View>());
   }
 
-  layout->StartRow(views::GridLayout::kFixedSize, 0);
   PaymentApp* selected_app = state()->selected_app();
   const mojom::PaymentItemPtr& total = spec()->GetTotal(selected_app);
   std::u16string total_label_text = base::UTF8ToUTF16(total->label);
-  std::unique_ptr<views::Label> total_label = CreateBoldLabel(total_label_text);
-  layout->AddView(std::move(total_label));
-
   std::u16string total_currency_code =
       base::UTF8ToUTF16(spec()->GetFormattedCurrencyCode(
           spec()->GetTotal(state()->selected_app())->amount));
   std::u16string total_amount = spec()->GetFormattedCurrencyAmount(
       spec()->GetTotal(state()->selected_app())->amount);
-  layout->AddView(CreateInlineCurrencyAmountItem(total_currency_code,
-                                                 total_amount, false, true));
+
+  view_builder.AddRows(1, views::TableLayout::kFixedSize, 0)
+      .AddChildren(
+          views::Builder<views::Label>(CreateBoldLabel(total_label_text)),
+          views::Builder<views::View>(CreateInlineCurrencyAmountItem(
+              total_currency_code, total_amount, false, true)));
 
   PaymentSheetRowBuilder builder(
       this, l10n_util::GetStringUTF16(IDS_PAYMENTS_ORDER_SUMMARY_LABEL));
@@ -591,7 +560,7 @@
               IDS_PAYMENT_REQUEST_ORDER_SUMMARY_SECTION_TOTAL_FORMAT,
               total_label_text, total_currency_code, total_amount)));
 
-  return builder.CreateWithChevron(std::move(inline_summary), nullptr);
+  return builder.CreateWithChevron(std::move(view_builder).Build(), nullptr);
 }
 
 std::unique_ptr<views::View>
@@ -690,29 +659,27 @@
               : base::BindRepeating(
                     &PaymentRequestDialogView::ShowPaymentMethodSheet,
                     dialog()));
+
   if (selected_app) {
-    auto content_view = std::make_unique<views::View>();
-
-    views::GridLayout* layout =
-        content_view->SetLayoutManager(std::make_unique<views::GridLayout>());
-    views::ColumnSet* columns = layout->AddColumnSet(0);
-    columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::CENTER,
-                       1.0, views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    layout->AddView(std::make_unique<views::Label>(selected_app->GetLabel()))
-        ->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-    layout->StartRow(views::GridLayout::kFixedSize, 0);
-    layout->AddView(std::make_unique<views::Label>(selected_app->GetSublabel()))
-        ->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    auto content_view =
+        views::Builder<views::BoxLayoutView>()
+            .SetOrientation(views::BoxLayout::Orientation::kVertical)
+            .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart)
+            .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kCenter)
+            .AddChildren(views::Builder<views::Label>()
+                             .SetText(selected_app->GetLabel())
+                             .SetHorizontalAlignment(gfx::ALIGN_LEFT),
+                         views::Builder<views::Label>()
+                             .SetText(selected_app->GetSublabel())
+                             .SetHorizontalAlignment(gfx::ALIGN_LEFT));
 
     std::unique_ptr<views::ImageView> icon_view = CreateAppIconView(
         selected_app->icon_resource_id(), selected_app->icon_bitmap(),
         selected_app->GetLabel());
 
     return builder.AccessibleContent(selected_app->GetLabel())
-        .CreateWithChevron(std::move(content_view), std::move(icon_view));
+        .CreateWithChevron(std::move(content_view).Build(),
+                           std::move(icon_view));
   }
   if (state()->available_apps().empty()) {
     return builder.CreateWithButton(std::u16string(),
@@ -858,15 +825,6 @@
 }
 
 std::unique_ptr<views::View> PaymentSheetViewController::CreateDataSourceRow() {
-  std::unique_ptr<views::View> content_view = std::make_unique<views::View>();
-  auto layout = std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical,
-      gfx::Insets(0, kPaymentRequestRowHorizontalInsets));
-  layout->set_main_axis_alignment(views::BoxLayout::MainAxisAlignment::kStart);
-  layout->set_cross_axis_alignment(
-      views::BoxLayout::CrossAxisAlignment::kStart);
-  content_view->SetLayoutManager(std::move(layout));
-
   std::u16string data_source;
   // If no transaction has been completed so far, choose which string to display
   // as a function of the profile's signed in state. Otherwise, always show the
@@ -909,13 +867,6 @@
   data_source.erase(link_end, end_tag.size());
   data_source.erase(link_begin, begin_tag.size());
 
-  auto data_source_label = std::make_unique<views::StyledLabel>();
-  data_source_label->SetText(data_source);
-
-  data_source_label->SetBorder(views::CreateEmptyBorder(22, 0, 0, 0));
-  data_source_label->SetID(static_cast<int>(DialogViewID::DATA_SOURCE_LABEL));
-  data_source_label->SetDefaultTextStyle(views::style::STYLE_DISABLED);
-
   views::StyledLabel::RangeStyleInfo link_style =
       views::StyledLabel::RangeStyleInfo::CreateForLink(base::BindRepeating(
           [](base::WeakPtr<PaymentRequestDialogView> dialog) {
@@ -929,11 +880,21 @@
   // TODO(pbos): Investigate whether this override is necessary.
   link_style.override_color = gfx::kGoogleBlue700;
 
-  data_source_label->AddStyleRange(
-      gfx::Range(link_begin, link_begin + link_length), link_style);
-  data_source_label->SizeToFit(0);
-  content_view->AddChildView(data_source_label.release());
-  return content_view;
+  return views::Builder<views::BoxLayoutView>()
+      .SetOrientation(views::BoxLayout::Orientation::kVertical)
+      .SetInsideBorderInsets(gfx::Insets(0, kPaymentRequestRowHorizontalInsets))
+      .SetMainAxisAlignment(views::BoxLayout::MainAxisAlignment::kStart)
+      .SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kStart)
+      .AddChild(
+          views::Builder<views::StyledLabel>()
+              .SetText(data_source)
+              .SetBorder(views::CreateEmptyBorder(22, 0, 0, 0))
+              .SetID(static_cast<int>(DialogViewID::DATA_SOURCE_LABEL))
+              .SetDefaultTextStyle(views::style::STYLE_DISABLED)
+              .AddStyleRange(gfx::Range(link_begin, link_begin + link_length),
+                             link_style)
+              .SizeToFit(0))
+      .Build();
 }
 
 void PaymentSheetViewController::AddShippingButtonPressed() {
diff --git a/chrome/browser/ui/views/profiles/profile_picker_view.cc b/chrome/browser/ui/views/profiles/profile_picker_view.cc
index c273cce..693b4bf88 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view.cc
@@ -11,6 +11,7 @@
 #include "base/files/file_path.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/chrome_command_ids.h"
@@ -440,6 +441,7 @@
 }
 
 void ProfilePickerView::Clear() {
+  TRACE_EVENT1("browser,startup", "ProfilePickerView::Clear", "state", state_);
   if (state_ == kClosing)
     return;
 
@@ -530,6 +532,8 @@
 
 void ProfilePickerView::Display(ProfilePicker::EntryPoint entry_point) {
   DCHECK_NE(state_, kClosing);
+  TRACE_EVENT2("browser,startup", "ProfilePickerView::Display", "entry_point",
+               entry_point, "state", state_);
   // Record creation metrics.
   base::UmaHistogramEnumeration("ProfilePicker.Shown", entry_point);
   if (entry_point == ProfilePicker::EntryPoint::kOnStartup) {
@@ -564,6 +568,10 @@
 
 void ProfilePickerView::OnPickerProfileCreated(Profile* picker_profile,
                                                Profile::CreateStatus status) {
+  TRACE_EVENT2("browser,startup", "ProfilePickerView::OnPickerProfileCreated",
+               "profile_path",
+               (picker_profile ? picker_profile->GetPath().AsUTF8Unsafe() : ""),
+               "status", status);
   DCHECK_NE(status, Profile::CREATE_STATUS_LOCAL_FAIL);
   if (status != Profile::CREATE_STATUS_INITIALIZED)
     return;
@@ -573,6 +581,9 @@
 
 void ProfilePickerView::Init(Profile* picker_profile) {
   DCHECK_EQ(state_, kInitializing);
+  TRACE_EVENT1(
+      "browser,startup", "ProfilePickerView::Init", "profile_path",
+      (picker_profile ? picker_profile->GetPath().AsUTF8Unsafe() : ""));
   contents_ = content::WebContents::Create(
       content::WebContents::CreateParams(picker_profile));
   contents_->SetDelegate(this);
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 c0a88a0..205472b 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_view_browsertest.cc
@@ -4,12 +4,14 @@
 
 #include "chrome/browser/ui/views/profiles/profile_picker_view.h"
 
+#include "base/barrier_closure.h"
 #include "base/callback_helpers.h"
 #include "base/json/values_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
@@ -18,6 +20,7 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/interstitials/chrome_settings_page_helper.h"
+#include "chrome/browser/metrics/first_web_contents_profiler_base.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service.h"
 #include "chrome/browser/policy/cloud/user_policy_signin_service_factory.h"
 #include "chrome/browser/profiles/profile_attributes_entry.h"
@@ -65,6 +68,7 @@
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/signin/public/identity_manager/primary_account_mutator.h"
+#include "components/startup_metric_utils/browser/startup_metric_utils.h"
 #include "components/sync/base/sync_prefs.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_service.h"
@@ -292,6 +296,67 @@
   return feature_engagement::CreateTestTracker();
 }
 
+class PageNonEmptyPaintObserver : public content::WebContentsObserver {
+ public:
+  explicit PageNonEmptyPaintObserver(const GURL& url,
+                                     content::WebContents* web_contents)
+      : WebContentsObserver(web_contents),
+        barrier_closure_(base::BarrierClosure(2, run_loop_.QuitClosure())),
+        url_(url) {}
+
+  void Wait() {
+    // Check if the right page has already been painted or loaded.
+    if (web_contents()->GetLastCommittedURL() == url_) {
+      if (web_contents()->CompletedFirstVisuallyNonEmptyPaint())
+        DidFirstVisuallyNonEmptyPaint();
+      if (!web_contents()->IsLoading())
+        DidStopLoading();
+    }
+
+    run_loop_.Run();
+  }
+
+ private:
+  // WebContentsObserver:
+  void DidFirstVisuallyNonEmptyPaint() override {
+    // Making sure that the same event does not trigger the barrier twice.
+    if (did_paint_)
+      return;
+
+    did_paint_ = true;
+    barrier_closure_.Run();
+  }
+
+  void DidStopLoading() override {
+    ASSERT_EQ(web_contents()->GetLastCommittedURL(), url_);
+
+    // Making sure that the same event does not trigger the barrier twice.
+    if (did_load_)
+      return;
+
+    // It shouldn't technically be necessary to wait for load stop here, we do
+    // this to be consistent with the other tests relying on `WaitForLoadStop()`
+    did_load_ = true;
+    barrier_closure_.Run();
+  }
+
+  base::RunLoop run_loop_;
+  base::RepeatingClosure barrier_closure_;
+  GURL url_;
+
+  bool did_paint_ = false;
+  bool did_load_ = false;
+};
+
+// Waits for a first non empty paint for `target` and expects that it will load
+// the given `url`.
+void WaitForFirstNonEmptyPaint(const GURL& url, content::WebContents* target) {
+  ASSERT_NE(target, nullptr);
+
+  PageNonEmptyPaintObserver observer(url, target);
+  observer.Wait();
+}
+
 }  // namespace
 
 class ProfilePickerCreationFlowBrowserTest : public ProfilePickerTestBase {
@@ -1007,6 +1072,8 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest, OpenProfile) {
+  base::HistogramTester histogram_tester;
+
   AvatarToolbarButton::SetIPHMinDelayAfterCreationForTesting(base::Seconds(0));
   FeaturePromoControllerViews::BlockActiveWindowCheckForTesting();
   ASSERT_EQ(1u, BrowserList::GetInstance()->size());
@@ -1019,12 +1086,52 @@
   OpenProfileFromPicker(other_path, /*open_settings=*/false);
   // Browser for the profile is displayed.
   Browser* new_browser = BrowserAddedWaiter(2u).Wait();
-  WaitForLoadStop(GURL("chrome://newtab/"),
-                  new_browser->tab_strip_model()->GetActiveWebContents());
+  WaitForFirstNonEmptyPaint(
+      GURL("chrome://newtab/"),
+      new_browser->tab_strip_model()->GetActiveWebContents());
   EXPECT_EQ(new_browser->profile()->GetPath(), other_path);
   WaitForPickerClosed();
   // IPH is shown.
   EXPECT_TRUE(ProfileSwitchPromoHasBeenShown(new_browser));
+
+  // FirstProfileTime.* histograms aren't recorded because the picker
+  // is opened from the menu.
+  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix(
+                  "ProfilePicker.FirstProfileTime."),
+              testing::IsEmpty());
+}
+
+IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
+                       OpenProfileFromStartup) {
+  base::HistogramTester histogram_tester;
+  ASSERT_FALSE(ProfilePicker::IsOpen());
+
+  // Create a second profile.
+  base::FilePath other_path = CreateNewProfileWithoutBrowser();
+
+  // Open the picker.
+  ProfilePicker::Show(ProfilePicker::EntryPoint::kOnStartup);
+  EXPECT_TRUE(ProfilePicker::IsOpen());
+  WaitForLoadStop(GURL("chrome://profile-picker"));
+
+  // Open the new profile.
+  OpenProfileFromPicker(other_path, /*open_settings=*/false);
+
+  // Measurement of startup performance started.
+
+  // Browser for the profile is displayed.
+  Browser* new_browser = BrowserAddedWaiter(2u).Wait();
+  WaitForFirstNonEmptyPaint(
+      GURL("chrome://newtab/"),
+      new_browser->tab_strip_model()->GetActiveWebContents());
+  EXPECT_EQ(new_browser->profile()->GetPath(), other_path);
+  WaitForPickerClosed();
+
+  histogram_tester.ExpectTotalCount(
+      "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint", 1);
+  histogram_tester.ExpectUniqueSample(
+      "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason",
+      metrics::StartupProfilingFinishReason::kDone, 1);
 }
 
 IN_PROC_BROWSER_TEST_F(ProfilePickerCreationFlowBrowserTest,
diff --git a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
index 904ea33..51d0fc1e 100644
--- a/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
+++ b/chrome/browser/ui/views/safe_browsing/tailored_security_unconsented_modal.cc
@@ -61,6 +61,9 @@
                  l10n_util::GetStringUTF16(
                      IDS_TAILORED_SECURITY_UNCONSENTED_CANCEL_BUTTON));
 
+  RecordModalOutcomeAndRunCallback(TailoredSecurityOutcome::kShown,
+                                   base::DoNothing());
+
   SetAcceptCallback(base::BindOnce(
       RecordModalOutcomeAndRunCallback, TailoredSecurityOutcome::kAccepted,
       base::BindOnce(&EnableEsbAndShowSettings, web_contents_)));
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 63602ee..916ae159 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -185,6 +185,8 @@
 #include "ash/webui/shimless_rma/url_constants.h"
 #include "ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.h"
 #include "ash/webui/shortcut_customization_ui/url_constants.h"
+#include "ash/webui/system_extensions_internals_ui/system_extensions_internals_ui.h"
+#include "ash/webui/system_extensions_internals_ui/url_constants.h"
 #include "base/system/sys_info.h"
 #include "chrome/browser/app_mode/app_mode_utils.h"
 #include "chrome/browser/ash/arc/arc_util.h"
@@ -961,6 +963,10 @@
     return &NewComponentUI<ash::PersonalizationAppUI,
                            ChromePersonalizationAppUiDelegate>;
   }
+  if (url.host_piece() == ash::kChromeUISystemExtensionsInternalsHost &&
+      base::FeatureList::IsEnabled(ash::features::kSystemExtensions)) {
+    return &NewWebUI<ash::SystemExtensionsInternalsUI>;
+  }
 
 #if !defined(OFFICIAL_BUILD)
 #if !defined(USE_REAL_DBUS_CLIENTS)
diff --git a/chrome/browser/ui/webui/realbox/realbox.mojom b/chrome/browser/ui/webui/realbox/realbox.mojom
index c5aa16a3..16ceea4 100644
--- a/chrome/browser/ui/webui/realbox/realbox.mojom
+++ b/chrome/browser/ui/webui/realbox/realbox.mojom
@@ -69,6 +69,11 @@
   // ID of the group the suggestion belongs to. 0 if it does not belong to any.
   int32 suggestion_group_id;
   bool supports_deletion;
+  // Holds the common part of tail suggestion. Not every match has a tail
+  // suggestion prefix. For example, the tail suggestion prefix for "hobbit
+  // holes for sale in" is "hobbit holes for sale" and the match contents
+  // would be the text following "sale".
+  mojo_base.mojom.String16? tail_suggest_common_prefix;
 };
 
 struct SuggestionGroup {
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index fed5184..05adedd 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -253,6 +253,8 @@
                 match,
                 RealboxHandler::FocusState::kFocusedButtonRemoveSuggestion));
 
+    mojom_match->tail_suggest_common_prefix = match.tail_suggest_common_prefix;
+
     matches.push_back(std::move(mojom_match));
     line++;
   }
@@ -683,6 +685,11 @@
                                      bool default_match_changed) {
   DCHECK(controller == autocomplete_controller_.get());
 
+  // Prepend missing tail suggestion prefixes in results, if present.
+  if (base::FeatureList::IsEnabled(omnibox::kNtpRealboxTailSuggest)) {
+    autocomplete_controller_->SetTailSuggestCommonPrefixes();
+  }
+
   page_->AutocompleteResultChanged(CreateAutocompleteResult(
       autocomplete_controller_->input().text(),
       autocomplete_controller_->result(),
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index 3e21ece9..26d60334 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -13,8 +13,10 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/trace_event/trace_event.h"
 #include "base/values.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/metrics/first_web_contents_profiler_base.h"
 #include "chrome/browser/new_tab_page/chrome_colors/chrome_colors_service.h"
 #include "chrome/browser/new_tab_page/chrome_colors/generated_colors_info.h"
 #include "chrome/browser/profiles/profile.h"
@@ -242,6 +244,99 @@
 }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
+void RecordProfilingFinishReason(
+    metrics::StartupProfilingFinishReason finish_reason) {
+  base::UmaHistogramEnumeration(
+      "ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason",
+      finish_reason);
+}
+
+class FirstWebContentsProfilerForProfilePicker
+    : public metrics::FirstWebContentsProfilerBase {
+ public:
+  explicit FirstWebContentsProfilerForProfilePicker(
+      content::WebContents* web_contents,
+      base::TimeTicks pick_time);
+
+  FirstWebContentsProfilerForProfilePicker(
+      const FirstWebContentsProfilerForProfilePicker&) = delete;
+  FirstWebContentsProfilerForProfilePicker& operator=(
+      const FirstWebContentsProfilerForProfilePicker&) = delete;
+
+ protected:
+  // FirstWebContentsProfilerBase:
+  void RecordFinishReason(
+      metrics::StartupProfilingFinishReason finish_reason) override;
+  void RecordNavigationFinished(base::TimeTicks navigation_start) override;
+  void RecordFirstNonEmptyPaint() override;
+  bool WasStartupInterrupted() override;
+
+ private:
+  ~FirstWebContentsProfilerForProfilePicker() override;
+
+  const base::TimeTicks pick_time_;
+};
+
+FirstWebContentsProfilerForProfilePicker::
+    FirstWebContentsProfilerForProfilePicker(content::WebContents* web_contents,
+                                             base::TimeTicks pick_time)
+    : FirstWebContentsProfilerBase(web_contents), pick_time_(pick_time) {
+  DCHECK(!pick_time_.is_null());
+}
+
+FirstWebContentsProfilerForProfilePicker::
+    ~FirstWebContentsProfilerForProfilePicker() = default;
+
+void FirstWebContentsProfilerForProfilePicker::RecordFinishReason(
+    metrics::StartupProfilingFinishReason finish_reason) {
+  RecordProfilingFinishReason(finish_reason);
+}
+
+void FirstWebContentsProfilerForProfilePicker::RecordNavigationFinished(
+    base::TimeTicks navigation_start) {
+  // Nothing to record here for Profile Picker startups.
+}
+
+void FirstWebContentsProfilerForProfilePicker::RecordFirstNonEmptyPaint() {
+  const char histogram_name[] =
+      "ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint";
+  base::TimeTicks paint_time = base::TimeTicks::Now();
+  base::UmaHistogramLongTimes100(histogram_name, paint_time - pick_time_);
+  TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0("startup", histogram_name,
+                                                   this, pick_time_);
+  TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("startup", histogram_name,
+                                                 this, paint_time);
+}
+
+bool FirstWebContentsProfilerForProfilePicker::WasStartupInterrupted() {
+  // We're assuming that no interruptions block opening an existing profile
+  // from the profile picker. We would detect this by observing really high
+  // latency on the tracked metric, and can start tracking interruptions if we
+  // find that such cases occur.
+  return false;
+}
+
+void BeginFirstWebContentsProfiling(Browser* browser,
+                                    base::TimeTicks pick_time) {
+  content::WebContents* visible_contents =
+      metrics::FirstWebContentsProfilerBase::GetVisibleContents(browser);
+  if (!visible_contents) {
+    RecordProfilingFinishReason(metrics::StartupProfilingFinishReason::
+                                    kAbandonNoInitiallyVisibleContent);
+    return;
+  }
+
+  if (visible_contents->CompletedFirstVisuallyNonEmptyPaint()) {
+    RecordProfilingFinishReason(
+        metrics::StartupProfilingFinishReason::kAbandonAlreadyPaintedContent);
+    return;
+  }
+
+  // FirstWebContentsProfilerForProfilePicker owns itself and is also bound to
+  // |visible_contents|'s lifetime by observing WebContentsDestroyed().
+  new FirstWebContentsProfilerForProfilePicker(visible_contents, pick_time);
+}
+
 }  // namespace
 
 ProfilePickerHandler::ProfilePickerHandler() = default;
@@ -383,6 +478,8 @@
 void ProfilePickerHandler::HandleLaunchSelectedProfile(
     bool open_settings,
     const base::ListValue* args) {
+  TRACE_EVENT1("browser", "ProfilePickerHandler::HandleLaunchSelectedProfile",
+               "args", args->DebugString());
   if (args->GetList().empty())
     return;
   const base::Value& profile_path_value = args->GetList()[0];
@@ -441,6 +538,12 @@
   }
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
+  if (!creation_time_on_startup_.is_null() &&
+      // Avoid overriding the picked time if already recorded. This can happen
+      // for example if multiple profiles are picked: https://crbug.com/1277466.
+      profile_picked_time_on_startup_.is_null()) {
+    profile_picked_time_on_startup_ = base::TimeTicks::Now();
+  }
   profiles::SwitchToProfile(
       *profile_path, /*always_create=*/false,
       base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete,
@@ -833,10 +936,20 @@
     bool open_settings,
     Profile* profile,
     Profile::CreateStatus profile_create_status) {
+  TRACE_EVENT2("browser", "ProfilePickerHandler::OnSwitchToProfileComplete",
+               "profile_path", profile->GetPath().AsUTF8Unsafe(),
+               "create_status", profile_create_status);
   Browser* browser = chrome::FindAnyBrowser(profile, false);
   DCHECK(browser);
   DCHECK(browser->window());
 
+  // Measure startup time to display first web contents if the profile picker
+  // was displayed on startup and if the initiating action is instrumented. For
+  // example we don't record pick time for profile creations.
+  if (!profile_picked_time_on_startup_.is_null()) {
+    BeginFirstWebContentsProfiling(browser, profile_picked_time_on_startup_);
+  }
+
   // Only show the profile switch IPH when the user clicked the card, and there
   // are multiple profiles.
   std::vector<ProfileAttributesEntry*> entries =
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.h b/chrome/browser/ui/webui/signin/profile_picker_handler.h
index 026c3b3..ab92d9b 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.h
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.h
@@ -182,6 +182,11 @@
   // Creation time of the handler, to measure performance on startup. Only set
   // when the picker is shown on startup.
   base::TimeTicks creation_time_on_startup_;
+
+  // Time when the user picked a profile to open, to measure browser startup
+  // performance. Only set when the picker is shown on startup.
+  base::TimeTicks profile_picked_time_on_startup_;
+
   bool main_view_initialized_ = false;
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index e457ca8b..496005d 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/tab_search/tab_search_page_handler.h"
 
+#include <iterator>
 #include <memory>
 #include <set>
 #include <string>
@@ -12,6 +13,7 @@
 
 #include "base/base64.h"
 #include "base/metrics/histogram_functions.h"
+#include "base/ranges/algorithm.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
@@ -27,6 +29,7 @@
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_live_tab_context.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_enums.h"
 #include "chrome/browser/ui/tabs/tab_renderer_data.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
@@ -432,8 +435,18 @@
   tab_data->last_active_elapsed_text =
       GetLastActiveElapsedText(last_active_time_ticks);
 
-  if (base::FeatureList::IsEnabled(features::kTabSearchMediaTabs))
-    tab_data->alert_states = chrome::GetTabAlertStatesForContents(contents);
+  if (base::FeatureList::IsEnabled(features::kTabSearchMediaTabs)) {
+    std::vector<TabAlertState> alert_states =
+        chrome::GetTabAlertStatesForContents(contents);
+    // Currently, we only report media alert states.
+    base::ranges::copy_if(alert_states.begin(), alert_states.end(),
+                          std::back_inserter(tab_data->alert_states),
+                          [](TabAlertState alert) {
+                            return alert == TabAlertState::MEDIA_RECORDING ||
+                                   alert == TabAlertState::AUDIO_PLAYING ||
+                                   alert == TabAlertState::AUDIO_MUTING;
+                          });
+  }
 
   return tab_data;
 }
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util.cc b/chrome/browser/ui/webui/whats_new/whats_new_util.cc
index 73cee59..322cddc 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_util.cc
+++ b/chrome/browser/ui/webui/whats_new/whats_new_util.cc
@@ -59,19 +59,19 @@
   if (!base::FeatureList::IsEnabled(features::kChromeWhatsNewUI))
     return false;
 
-  // Show What's New if the page hasn't yet been shown for the current
-  // milestone.
   int last_version = local_state->GetInteger(prefs::kLastWhatsNewVersion);
 
-  return CHROME_VERSION_MAJOR > last_version;
-}
+  // Don't show What's New if it's already been shown for the current major
+  // milestone.
+  if (CHROME_VERSION_MAJOR <= last_version)
+    return false;
 
-void SetLastVersion(PrefService* local_state) {
-  if (!local_state) {
-    return;
-  }
-
+  // Set the last version here to indicate that What's New should not attempt
+  // to display again for this milestone. This prevents the page from
+  // potentially displaying multiple times in a given milestone, e.g. for
+  // multiple profile relaunches (see https://crbug.com/1274313).
   local_state->SetInteger(prefs::kLastWhatsNewVersion, CHROME_VERSION_MAJOR);
+  return true;
 }
 
 GURL GetServerURL(bool may_redirect) {
@@ -191,11 +191,6 @@
 
     DCHECK(browser_);
 
-    // Update pref if shown automatically. Do this even if the load failed - we
-    // only want to try once, so that the network request only occurs once per
-    // version and not every time the browser opens.
-    SetLastVersion(g_browser_process->local_state());
-
     LogLoadEvent(success ? LoadEvent::kLoadSuccess
                          : LoadEvent::kLoadFailAndDoNotShow);
     if (success)
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_util.h b/chrome/browser/ui/webui/whats_new/whats_new_util.h
index 4c341af..05f8e7e 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_util.h
+++ b/chrome/browser/ui/webui/whats_new/whats_new_util.h
@@ -35,12 +35,20 @@
 // DisableRemoteContentForTests().
 bool IsRemoteContentDisabled();
 
-// Whether the What's New page should be shown, based on |local_state|.
+// Returns true if the user has not yet seen the What's New page for the
+// current major milestone. When returning true, sets the pref in |local_state|
+// to indicate that What's New should not try to display again for the current
+// major milestone.
+// Note that this does not guarantee that the page will always show (for
+// example, onboarding tabs override What's New, or remote content can fail to
+// load, which will result in the tab not opening). However, What's New should
+// only display automatically on the first relaunch after updating to a new
+// major milestone, and it is preferable to only attempt to show the page once
+// and possibly miss some users instead of repeatedly triggering a network
+// request at startup and/or showing the same What's New page many times for a
+// given user.
 bool ShouldShowForState(PrefService* local_state);
 
-// Sets the last What's New version in |local_state| to the current version.
-void SetLastVersion(PrefService* local_state);
-
 // Gets the server side URL for the What's New page for the current version of
 // Chrome. If |may_redirect| is true, return a server URL that will redirect to
 // the closest milestone page. Otherwise, return the direct URL of the current
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index c1d4c58..892f302 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1639483122-6c64e4735696edd79f94e428f8b51a0056079d88.profdata
+chrome-win32-main-1639493922-9a45d06329fbed3e310b5994955083e355361eb8.profdata
diff --git a/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
index 9b846fcd..d289305 100644
--- a/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
+++ b/chrome/chrome_cleaner/parsers/json_parser/test_json_parser.cc
@@ -14,7 +14,8 @@
                            ParseDoneCallback callback) {
   base::JSONReader::ValueWithError value_with_error =
       base::JSONReader::ReadAndReturnValueWithError(
-          json, base::JSON_ALLOW_TRAILING_COMMAS |
+          json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+                    base::JSON_ALLOW_TRAILING_COMMAS |
                     base::JSON_REPLACE_INVALID_CHARACTERS);
   if (value_with_error.value) {
     std::move(callback).Run(std::move(value_with_error.value), absl::nullopt);
diff --git a/chrome/chrome_cleaner/parsers/target/parser_impl.cc b/chrome/chrome_cleaner/parsers/target/parser_impl.cc
index 7db38ae8..69b2baf 100644
--- a/chrome/chrome_cleaner/parsers/target/parser_impl.cc
+++ b/chrome/chrome_cleaner/parsers/target/parser_impl.cc
@@ -25,7 +25,8 @@
                            ParseJsonCallback callback) {
   base::JSONReader::ValueWithError parsed_json =
       base::JSONReader::ReadAndReturnValueWithError(
-          json, base::JSON_ALLOW_TRAILING_COMMAS |
+          json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+                    base::JSON_ALLOW_TRAILING_COMMAS |
                     base::JSON_REPLACE_INVALID_CHARACTERS);
   if (parsed_json.value) {
     std::move(callback).Run(std::move(parsed_json.value), absl::nullopt);
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index 88286bc..b676c7b 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -214,6 +214,7 @@
         "$root_gen_dir/ash/ash_scanning_app_resources.pak",
         "$root_gen_dir/ash/ash_shimless_rma_resources.pak",
         "$root_gen_dir/ash/ash_shortcut_customization_app_resources.pak",
+        "$root_gen_dir/ash/ash_system_extensions_internals_resources.pak",
         "$root_gen_dir/ash/connectivity_diagnostics_resources.pak",
         "$root_gen_dir/ash/public/cpp/resources/ash_public_unscaled_resources.pak",
         "$root_gen_dir/ash/webui/file_manager/resources/file_manager_swa_resources.pak",
@@ -268,6 +269,7 @@
         "//ash/webui/resources:scanning_app_resources",
         "//ash/webui/resources:shimless_rma_resources",
         "//ash/webui/resources:shortcut_customization_app_resources",
+        "//ash/webui/resources:system_extensions_internals_resources",
         "//chrome/browser/resources:bluetooth_pairing_dialog_resources",
         "//chrome/browser/resources:internet_config_dialog_resources",
         "//chrome/browser/resources:internet_detail_dialog_resources",
diff --git a/chrome/common/extensions/api/extension.json b/chrome/common/extensions/api/extension.json
index 86c407f..ac9f029 100644
--- a/chrome/common/extensions/api/extension.json
+++ b/chrome/common/extensions/api/extension.json
@@ -156,38 +156,34 @@
         "type": "function",
         "description": "Retrieves the state of the extension's access to Incognito-mode. This corresponds to the user-controlled per-extension 'Allowed in Incognito' setting accessible via the chrome://extensions page.",
         "min_version": "12.0.706.0",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "isAllowedAccess",
-                "type": "boolean",
-                "description": "True if the extension has access to Incognito mode, false otherwise."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "isAllowedAccess",
+              "type": "boolean",
+              "description": "True if the extension has access to Incognito mode, false otherwise."
+            }
+          ]
+        }
       },
       {
         "name": "isAllowedFileSchemeAccess",
         "type": "function",
         "description": "Retrieves the state of the extension's access to the 'file://' scheme. This corresponds to the user-controlled per-extension 'Allow access to File URLs' setting accessible via the chrome://extensions page.",
         "min_version": "12.0.706.0",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "isAllowedAccess",
-                "type": "boolean",
-                "description": "True if the extension can access the 'file://' scheme, false otherwise."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "isAllowedAccess",
+              "type": "boolean",
+              "description": "True if the extension can access the 'file://' scheme, false otherwise."
+            }
+          ]
+        }
       },
       {
         "name": "setUpdateUrlData",
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
index 2310f8d..60806a74 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
@@ -109,7 +109,8 @@
 
     result = base::JSONReader::Read(
         base::StringPiece(response_.data(), response_.size()),
-        base::JSON_ALLOW_TRAILING_COMMAS);
+        base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+            base::JSON_ALLOW_TRAILING_COMMAS);
     if (!result || !result->is_dict()) {
       LOGFN(ERROR) << "Failed to read json result from server response";
       result.reset();
diff --git a/chrome/installer/mac/BUILD.gn b/chrome/installer/mac/BUILD.gn
index a2d2713..9bd016f 100644
--- a/chrome/installer/mac/BUILD.gn
+++ b/chrome/installer/mac/BUILD.gn
@@ -166,7 +166,7 @@
       # Help the compiler find lipo for creating fat binaries.
       "-B",
       mac_bin_path,
-
+      "-fno-lto",
       "-arch",
       "x86_64",
       "-arch",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index da7a68b..9f95790 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1691,7 +1691,6 @@
       "../browser/optimization_guide/browser_test_util.h",
       "../browser/optimization_guide/hints_fetcher_browsertest.cc",
       "../browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc",
-      "../browser/optimization_guide/page_content_annotations_service_browsertest.cc",
       "../browser/optimization_guide/page_text_observer_browsertest.cc",
       "../browser/optimization_guide/prediction/prediction_manager_browsertest.cc",
       "../browser/page_load_metrics/observers/ad_metrics/ad_density_intervention_browsertest.cc",
@@ -2120,6 +2119,13 @@
       "v8/wasm_trap_handler_browsertest.cc",
     ]
 
+    # crbug.com/1279884 Flaky on CrOS
+    if (!is_chromeos) {
+      sources += [
+          "../browser/optimization_guide/page_content_annotations_service_browsertest.cc"
+      ]
+    }
+
     if (enable_reporting) {
       sources += [ "../browser/net/reporting_browsertest.cc" ]
     }
@@ -2404,6 +2410,7 @@
           "//ash/webui/media_app_ui:browser_tests_js",
           "//ash/webui/personalization_app:browser_tests_js",
           "//ash/webui/system_apps:browser_tests",
+          "//ash/webui/system_extensions_internals_ui:browser_tests_js",
           "//chrome/browser/resources/gaia_auth_host:browser_tests",
         ]
 
diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json b/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json
index c8ab892ab..712e74d 100644
--- a/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json
+++ b/chrome/test/data/extensions/api_test/declarative_net_request/fenced_frames/rules.json
@@ -4,19 +4,23 @@
   "action": { "type" : "block" },
   "condition" : { "urlFilter" : "blocked", "resourceTypes" : [ "sub_frame" ] }
 },
-// Add a rule to block any *main* frames with "allowed". Since the only
-// frame matching "allowed" is a fenced frame, it shouldn't match this
-// rule.
 {
+  "_comment": [
+    "Add a rule to block any *main* frames with 'allowed'. Since the only",
+    "frame matching 'allowed' is a fenced frame, it shouldn't match this",
+    "rule."
+  ],
   "id" : 2,
   "priority": 1,
   "action": { "type" : "block" },
   "condition" : { "urlFilter" : "allowed", "resourceTypes" : [ "main_frame" ] }
 },
-// Add a rule to block any thirdParty frames with "allowed". Since the only
-// frame matching "allowed" is a fenced frame and is considered firstParty
-// it shouldn't match this rule.
 {
+  "_comment": [
+    "Add a rule to block any thirdParty frames with 'allowed'. Since the only",
+    "frame matching 'allowed' is a fenced frame and is considered firstParty",
+    "it shouldn't match this rule."
+  ],
   "id" : 3,
   "priority": 2,
   "action": { "type" : "block" },
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 2254eadc..75d6773f 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -12095,9 +12095,6 @@
   "SystemTimezoneAutomaticDetection": {
     "reason_for_missing_test": "Maps into CrosSettings"
   },
-  "WebRestrictionsAuthority": {
-    "reason_for_missing_test": "TODO(crbug.com/1213429) add test case"
-  },
   "TaskManagerEndProcessEnabled": {
     "os": [
       "win",
diff --git a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
index 58992a9..55437c2 100644
--- a/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
+++ b/chrome/test/data/webui/new_tab_page/realbox/realbox_test.js
@@ -214,11 +214,15 @@
       match.answer ? match.answer.secondLine : match.description);
   const separatorText =
       matchDescription ? loadTimeData.getString('realboxSeparator') : '';
+  const contents = matchEl.$['contents'].textContent.trim();
+  const separator = matchEl.$['separator'].textContent.trim();
+  const description = matchEl.$['description'].textContent.trim();
+  const text = (contents + ' ' + separator + ' ' + description).trim();
   assertEquals(
       match.swapContentsAndDescription ?
           matchDescription + separatorText + matchContents :
           matchContents + separatorText + matchDescription,
-      matchEl.$['text-container'].textContent.trim());
+      text);
 }
 
 suite('NewTabPageRealboxTest', () => {
diff --git a/chrome/updater/app/app_update.cc b/chrome/updater/app/app_update.cc
index 0da2c89..6dca67c 100644
--- a/chrome/updater/app/app_update.cc
+++ b/chrome/updater/app/app_update.cc
@@ -30,17 +30,12 @@
   void FirstTaskRun() override;
 
   void SetupDone(int result);
-
-  scoped_refptr<Configurator> config_;
 };
 
 void AppUpdate::Initialize() {
-  config_ = base::MakeRefCounted<Configurator>(
-      CreateGlobalPrefs(updater_scope()), CreateExternalConstants());
 }
 
 void AppUpdate::Uninitialize() {
-  PrefsCommitPendingWrites(config_->GetPrefService());
 }
 
 void AppUpdate::FirstTaskRun() {
diff --git a/chrome/updater/test/integration_test_commands.h b/chrome/updater/test/integration_test_commands.h
index 8a9464d..29090b6 100644
--- a/chrome/updater/test/integration_test_commands.h
+++ b/chrome/updater/test/integration_test_commands.h
@@ -60,7 +60,7 @@
   virtual void UpdateAll() const = 0;
   virtual void PrintLog() const = 0;
   virtual base::FilePath GetDifferentUserPath() const = 0;
-  virtual void WaitForUpdaterExit() const = 0;
+  virtual void WaitForServerExit() const = 0;
 #if defined(OS_WIN)
   virtual void ExpectInterfacesRegistered() const = 0;
   virtual void ExpectLegacyUpdate3WebSucceeds(
diff --git a/chrome/updater/test/integration_test_commands_system.cc b/chrome/updater/test/integration_test_commands_system.cc
index b016a44..16ebb5f 100644
--- a/chrome/updater/test/integration_test_commands_system.cc
+++ b/chrome/updater/test/integration_test_commands_system.cc
@@ -146,8 +146,8 @@
     RunCommand("register_app", {Param("app_id", app_id)});
   }
 
-  void WaitForUpdaterExit() const override {
-    updater::test::WaitForUpdaterExit(updater_scope_);
+  void WaitForServerExit() const override {
+    updater::test::WaitForServerExit(updater_scope_);
   }
 
 #if defined(OS_WIN)
diff --git a/chrome/updater/test/integration_test_commands_user.cc b/chrome/updater/test/integration_test_commands_user.cc
index c70a01d5..cc1997a 100644
--- a/chrome/updater/test/integration_test_commands_user.cc
+++ b/chrome/updater/test/integration_test_commands_user.cc
@@ -135,8 +135,8 @@
     updater::test::RegisterApp(updater_scope_, app_id);
   }
 
-  void WaitForUpdaterExit() const override {
-    updater::test::WaitForUpdaterExit(updater_scope_);
+  void WaitForServerExit() const override {
+    updater::test::WaitForServerExit(updater_scope_);
   }
 
 #if defined(OS_WIN)
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index be20f5ca..f9fbc75b 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -120,7 +120,6 @@
     PrintLog();
     CopyLog();
     test_commands_->Uninstall();
-    WaitForUpdaterExit();
   }
 
   void ExpectCandidateUninstalled() {
@@ -212,7 +211,7 @@
     return test_commands_->GetDifferentUserPath();
   }
 
-  void WaitForUpdaterExit() { test_commands_->WaitForUpdaterExit(); }
+  void WaitForServerExit() { test_commands_->WaitForServerExit(); }
 
   void SetUpTestService() {
 #if defined(OS_WIN)
@@ -266,7 +265,7 @@
 
 TEST_F(IntegrationTest, InstallUninstall) {
   Install();
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectInstalled();
   ExpectVersionActive(kUpdaterVersion);
   ExpectActiveUpdater();
@@ -282,12 +281,16 @@
 TEST_F(IntegrationTest, SelfUninstallOutdatedUpdater) {
   Install();
   ExpectInstalled();
-  WaitForUpdaterExit();
+  SleepFor(2);
   SetupFakeUpdaterHigherVersion();
   ExpectVersionNotActive(kUpdaterVersion);
 
   RunWake(0);
-  WaitForUpdaterExit();
+
+  // The mac server will remain active for 10 seconds after it replies to the
+  // wake client, then shut down and uninstall itself. Sleep to wait for this
+  // to happen.
+  SleepFor(11);
 
   ExpectCandidateUninstalled();
   // The candidate uninstall should not have altered global prefs.
@@ -303,7 +306,7 @@
   ExpectRegistrationEvent(&test_server, kUpdaterAppId);
   Install();
   ExpectInstalled();
-  WaitForUpdaterExit();
+  WaitForServerExit();
   SetupFakeUpdaterLowerVersion();
   ExpectVersionNotActive(kUpdaterVersion);
 
@@ -312,7 +315,7 @@
                        base::Version("0.2"));
 
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
 
   // This instance is now qualified and should activate itself and check itself
   // for updates on the next check.
@@ -321,7 +324,7 @@
                            base::StringPrintf(".*%s.*", kUpdaterAppId))},
       ")]}'\n");
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectVersionActive(kUpdaterVersion);
 
   Uninstall();
@@ -338,7 +341,7 @@
                        base::Version(kUpdaterVersion), next_version);
 
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectAppVersion(kUpdaterAppId, next_version);
 
   Uninstall();
@@ -401,7 +404,7 @@
   base::Version v2("2");
   ExpectUpdateSequence(&test_server, kAppId, v1, v2);
   Update(kAppId);
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectAppVersion(kAppId, v2);
 
   Uninstall();
@@ -499,7 +502,8 @@
   ExpectActiveUpdater();
 
   RunUninstallCmdLine();
-  WaitForUpdaterExit();
+  WaitForServerExit();
+  SleepFor(2);
   ExpectClean();
 }
 #endif  // defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
@@ -510,14 +514,14 @@
   RegisterApp("test1");
   RegisterApp("test2");
 
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectVersionActive(kUpdaterVersion);
   ExpectActiveUpdater();
   SetExistenceCheckerPath("test1", base::FilePath(FILE_PATH_LITERAL("NONE")));
 
   RunWake(0);
 
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectInstalled();
   ExpectAppUnregisteredExistenceCheckerPath("test1");
 
@@ -526,11 +530,12 @@
 
 TEST_F(IntegrationTest, UninstallIfMaxServerWakesBeforeRegistrationExceeded) {
   Install();
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectInstalled();
   SetServerStarts(24);
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
+  SleepFor(2);
   ExpectClean();
 }
 
@@ -538,16 +543,17 @@
   Install();
   RegisterApp("test1");
   ExpectInstalled();
-  WaitForUpdaterExit();
+  WaitForServerExit();
   SetServerStarts(24);
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
   ExpectInstalled();
   ExpectVersionActive(kUpdaterVersion);
   ExpectActiveUpdater();
   SetExistenceCheckerPath("test1", base::FilePath(FILE_PATH_LITERAL("NONE")));
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
+  SleepFor(2);
   ExpectClean();
 }
 
@@ -566,7 +572,7 @@
   SetExistenceCheckerPath("test1", GetDifferentUserPath());
 
   RunWake(0);
-  WaitForUpdaterExit();
+  WaitForServerExit();
 
   ExpectAppUnregisteredExistenceCheckerPath("test1");
 
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index 06e90b0..9ca5207 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -313,6 +313,18 @@
   return process.WaitForExitWithTimeout(base::Seconds(45), exit_code);
 }
 
+void SleepFor(int seconds) {
+  VLOG(2) << "Sleeping " << seconds << " seconds...";
+  base::WaitableEvent sleep(base::WaitableEvent::ResetPolicy::MANUAL,
+                            base::WaitableEvent::InitialState::NOT_SIGNALED);
+  base::ThreadPool::PostDelayedTask(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&sleep)),
+      base::Seconds(seconds));
+  sleep.Wait();
+  VLOG(2) << "Sleep complete.";
+}
+
 bool WaitFor(base::RepeatingCallback<bool()> predicate) {
   base::TimeTicks deadline =
       base::TimeTicks::Now() + TestTimeouts::action_max_timeout();
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index 7e59378..dc8c38b 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -51,6 +51,11 @@
 // Copies the logs to a location where they can be retrieved by ResultDB.
 void CopyLog(const base::FilePath& src_dir);
 
+// Sleeps for the given number of seconds. This should be avoided, but in some
+// cases surrounding uninstall it is necessary since the processes can exit
+// prior to completing the actual uninstallation.
+void SleepFor(int seconds);
+
 // Waits for a given predicate to become true, testing it by polling. Returns
 // true if the predicate becomes true before a timeout, otherwise returns false.
 bool WaitFor(base::RepeatingCallback<bool()> predicate);
@@ -138,7 +143,7 @@
 
 void RegisterApp(UpdaterScope scope, const std::string& app_id);
 
-void WaitForUpdaterExit(UpdaterScope scope);
+void WaitForServerExit(UpdaterScope scope);
 
 #if defined(OS_WIN)
 void ExpectInterfacesRegistered(UpdaterScope scope);
diff --git a/chrome/updater/test/integration_tests_linux.cc b/chrome/updater/test/integration_tests_linux.cc
index 6850c64..d4fb5b6 100644
--- a/chrome/updater/test/integration_tests_linux.cc
+++ b/chrome/updater/test/integration_tests_linux.cc
@@ -27,7 +27,7 @@
   return absl::nullopt;
 }
 
-void WaitForUpdaterExit(UpdaterScope scope) {
+void WaitForServerExit(UpdaterScope scope) {
   NOTREACHED();
 }
 
diff --git a/chrome/updater/test/integration_tests_mac.mm b/chrome/updater/test/integration_tests_mac.mm
index 4ff2314..89aa572b 100644
--- a/chrome/updater/test/integration_tests_mac.mm
+++ b/chrome/updater/test/integration_tests_mac.mm
@@ -300,7 +300,7 @@
   EXPECT_FALSE(base::PathIsWritable(*path));
 }
 
-void WaitForUpdaterExit(UpdaterScope /*scope*/) {
+void WaitForServerExit(UpdaterScope /*scope*/) {
   ASSERT_TRUE(WaitFor(base::BindRepeating([]() {
     std::string ps_stdout;
     EXPECT_TRUE(base::GetAppOutput({"ps", "ax", "-o", "command"}, &ps_stdout));
diff --git a/chrome/updater/test/integration_tests_win.cc b/chrome/updater/test/integration_tests_win.cc
index 1a29f59ff..e7bbeacb 100644
--- a/chrome/updater/test/integration_tests_win.cc
+++ b/chrome/updater/test/integration_tests_win.cc
@@ -290,8 +290,8 @@
 // Returns true is any updater process is found running in any session in the
 // system, regardless of its path.
 bool IsUpdaterRunning() {
-  return IsProcessRunning(kUpdaterProcessName) ||
-         IsProcessRunning(base::UTF8ToWide(kUninstallScript).c_str());
+  ProcessFilterName filter(kUpdaterProcessName);
+  return base::ProcessIterator(&filter).NextProcessEntry();
 }
 
 }  // namespace
@@ -411,6 +411,10 @@
   int exit_code = -1;
   ASSERT_TRUE(Run(scope, command_line, &exit_code));
   EXPECT_EQ(0, exit_code);
+
+  // Uninstallation involves a race with the uninstall.cmd script and the
+  // process exit. Sleep to allow the script to complete its work.
+  SleepFor(5);
 }
 
 void SetActive(UpdaterScope /*scope*/, const std::string& id) {
@@ -446,7 +450,7 @@
 
 // Waits for all updater processes to end, including the server process holding
 // the prefs lock.
-void WaitForUpdaterExit(UpdaterScope /*scope*/) {
+void WaitForServerExit(UpdaterScope /*scope*/) {
   WaitFor(base::BindRepeating([]() { return !IsUpdaterRunning(); }));
 }
 
diff --git a/chromecast/app/BUILD.gn b/chromecast/app/BUILD.gn
index aa703d36..0b84bbd 100644
--- a/chromecast/app/BUILD.gn
+++ b/chromecast/app/BUILD.gn
@@ -52,6 +52,7 @@
       "//chromecast:chromecast_buildflags",
       "//chromecast/base",
       "//chromecast/browser",
+      "//chromecast/browser/migration",
       "//chromecast/cast_core:cast_runtime_content_client_factories",
       "//chromecast/common",
       "//chromecast/common:resource_delegate",
diff --git a/chromeos/components/onc/onc_test_utils.cc b/chromeos/components/onc/onc_test_utils.cc
index 3be625d..6832634 100644
--- a/chromeos/components/onc/onc_test_utils.cc
+++ b/chromeos/components/onc/onc_test_utils.cc
@@ -65,8 +65,9 @@
     LOG(FATAL) << "Unable to get test file path for: " << filename;
     return result;
   }
-  JSONFileValueDeserializer deserializer(path,
-                                         base::JSON_ALLOW_TRAILING_COMMAS);
+  JSONFileValueDeserializer deserializer(
+      path,
+      base::JSON_PARSE_CHROMIUM_EXTENSIONS | base::JSON_ALLOW_TRAILING_COMMAS);
   std::string error_message;
   result = deserializer.Deserialize(nullptr, &error_message);
   CHECK(result != nullptr) << "Couldn't json-deserialize file: " << filename
diff --git a/chromeos/components/onc/onc_utils.cc b/chromeos/components/onc/onc_utils.cc
index 8a298fc3..4fa47c78 100644
--- a/chromeos/components/onc/onc_utils.cc
+++ b/chromeos/components/onc/onc_utils.cc
@@ -332,7 +332,8 @@
   }
   base::JSONReader::ValueWithError parsed_json =
       base::JSONReader::ReadAndReturnValueWithError(
-          json, base::JSON_ALLOW_TRAILING_COMMAS);
+          json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
+                    base::JSON_ALLOW_TRAILING_COMMAS);
   if (!parsed_json.value || !parsed_json.value->is_dict()) {
     NET_LOG(ERROR) << "Invalid JSON Dictionary: " << parsed_json.error_message;
     return base::Value();
diff --git a/chromeos/network/client_cert_resolver_unittest.cc b/chromeos/network/client_cert_resolver_unittest.cc
index 50865353..aa07f02 100644
--- a/chromeos/network/client_cert_resolver_unittest.cc
+++ b/chromeos/network/client_cert_resolver_unittest.cc
@@ -376,7 +376,8 @@
                                base::StringPiece policy_json) {
     base::JSONReader::ValueWithError parsed_json =
         base::JSONReader::ReadAndReturnValueWithError(
-            policy_json, base::JSON_ALLOW_TRAILING_COMMAS);
+            policy_json,
+            base::JSON_ALLOW_TRAILING_COMMAS | base::JSON_ALLOW_CONTROL_CHARS);
     ASSERT_TRUE(parsed_json.value) << parsed_json.error_message;
 
     base::ListValue* policy = nullptr;
diff --git a/chromeos/network/network_sms_handler.cc b/chromeos/network/network_sms_handler.cc
index e65eba4..a0636db 100644
--- a/chromeos/network/network_sms_handler.cc
+++ b/chromeos/network/network_sms_handler.cc
@@ -336,8 +336,9 @@
 
   const std::string* object_path_string =
       properties->FindStringKey(shill::kDBusObjectProperty);
-  if (!object_path_string) {
-    NET_LOG(ERROR) << "Device has no DBusObject Property: " << device_path;
+  if (!object_path_string || object_path_string->empty()) {
+    NET_LOG(ERROR) << "Device has no or empty DBusObject Property: "
+                   << device_path;
     return;
   }
   dbus::ObjectPath object_path(*object_path_string);
diff --git a/chromeos/network/network_sms_handler_unittest.cc b/chromeos/network/network_sms_handler_unittest.cc
index 5b99bb25..1a03d5e7 100644
--- a/chromeos/network/network_sms_handler_unittest.cc
+++ b/chromeos/network/network_sms_handler_unittest.cc
@@ -130,6 +130,19 @@
   EXPECT_NE(messages.find(kMessage1), messages.end());
 }
 
+TEST_F(NetworkSmsHandlerTest, SmsHandlerEmptyDbusObjectPath) {
+  // This test verifies no crash should occur when the device dbus object path
+  // is an empty value.
+  device_test_->SetDeviceProperty(kCellularDevicePath,
+                                  shill::kDBusObjectProperty, base::Value(""),
+                                  /*notify_changed=*/true);
+  base::RunLoop().RunUntilIdle();
+  network_sms_handler_->RequestUpdate();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(test_observer_->message_count(), 0);
+}
+
 TEST_F(NetworkSmsHandlerTest, SmsHandlerDeviceObjectPathChange) {
   // Fake the SIM being switched to a different SIM.
   device_test_->SetDeviceProperty(
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index bd0dbb3..d5652da 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-98-4729.0-1638787322-benchmark-98.0.4758.3-r1.orderfile.xz
+chromeos-chrome-orderfile-field-98-4729.0-1638787322-benchmark-98.0.4758.5-r1.orderfile.xz
diff --git a/chromeos/services/bluetooth_config/device_cache_impl.cc b/chromeos/services/bluetooth_config/device_cache_impl.cc
index 0e967128..7e689b1 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl.cc
+++ b/chromeos/services/bluetooth_config/device_cache_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/containers/contains.h"
 #include "chromeos/services/bluetooth_config/device_conversion_util.h"
+#include "device/bluetooth/chromeos/bluetooth_utils.h"
 
 namespace chromeos {
 namespace bluetooth_config {
@@ -156,8 +157,10 @@
 }
 
 void DeviceCacheImpl::FetchInitialDeviceLists() {
-  for (const device::BluetoothDevice* device :
-       bluetooth_adapter_->GetDevices()) {
+  device::BluetoothAdapter::DeviceList devices = FilterBluetoothDeviceList(
+      bluetooth_adapter_->GetDevices(), device::BluetoothFilterType::KNOWN,
+      /*max_devices=*/0);
+  for (const device::BluetoothDevice* device : devices) {
     if (device->IsPaired()) {
       paired_devices_.push_back(
           GeneratePairedBluetoothDeviceProperties(device));
@@ -204,7 +207,12 @@
         return paired_device->device_properties->id;
       });
 
-  // If device is not found in |paired_devices|, don't update.
+  // If device is not found in |paired_devices|, don't update. This is done
+  // because when a paired device is forgotten, it is removed from
+  // |paired_devices|, but then OnDeviceChanged() is called with
+  // device->IsPaired() == true. If we don't have this check here, the device
+  // will be incorrectly added back into |paired_devices|. See
+  // crrev.com/c/3287422.
   if (!device_found)
     return false;
 
@@ -230,6 +238,10 @@
   if (device->IsPaired())
     return false;
 
+  // Check if the device should be added to the unpaired device list.
+  if (device::IsUnsupportedDevice(device))
+    return false;
+
   // Remove the old (stale) properties, if they exist.
   RemoveFromUnpairedDeviceList(device);
 
@@ -254,16 +266,6 @@
 
 bool DeviceCacheImpl::AttemptUpdateUnpairedDeviceMetadata(
     device::BluetoothDevice* device) {
-  bool device_found =
-      base::Contains(unpaired_devices_, device->GetIdentifier(),
-                     [](const auto& unpaired_device) {
-                       return unpaired_device->device_properties->id;
-                     });
-
-  // If device is not found in |unpaired_devices|, don't update.
-  if (!device_found)
-    return false;
-
   // Remove existing metadata about |device|.
   bool updated = RemoveFromUnpairedDeviceList(device);
 
diff --git a/chromeos/services/bluetooth_config/device_cache_impl.h b/chromeos/services/bluetooth_config/device_cache_impl.h
index 0492b93..13e05ff 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl.h
+++ b/chromeos/services/bluetooth_config/device_cache_impl.h
@@ -119,8 +119,8 @@
   bool RemoveFromUnpairedDeviceList(device::BluetoothDevice* device);
 
   // Attempts to add updated metadata about |device| to |paired_devices_|. If
-  // |device| is not found in |unpaired_devices_|, no update is performed.
-  // Returns true if the device was updated in the list.
+  // |device| is not found in |unpaired_devices_|, it is added. Returns true if
+  // the device was updated in the list.
   bool AttemptUpdateUnpairedDeviceMetadata(device::BluetoothDevice* device);
 
   // Sorts |unpaired_devices_| based on signal strength. This function is called
diff --git a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
index 4f823560..6ef722f 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
+++ b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
@@ -94,7 +94,9 @@
   void AddDevice(bool paired,
                  bool connected,
                  std::string* id_out,
-                 const absl::optional<int8_t> inquiry_rssi = absl::nullopt) {
+                 const absl::optional<int8_t> inquiry_rssi = absl::nullopt,
+                 const device::BluetoothDeviceType device_type =
+                     device::BluetoothDeviceType::AUDIO) {
     // We use the number of devices created in this test as the address.
     std::string address = base::NumberToString(num_devices_created_);
     ++num_devices_created_;
@@ -106,8 +108,12 @@
         std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
             mock_adapter_.get(), kTestBluetoothClass, kTestBluetoothName,
             address, paired, connected);
+    ON_CALL(*mock_device, GetType())
+        .WillByDefault(testing::Return(device::BLUETOOTH_TRANSPORT_DUAL));
     ON_CALL(*mock_device, GetInquiryRSSI())
         .WillByDefault(testing::Return(inquiry_rssi));
+    ON_CALL(*mock_device, GetDeviceType())
+        .WillByDefault(testing::Return(device_type));
 
     device::BluetoothDevice* device = mock_device.get();
     mock_devices_.push_back(std::move(mock_device));
@@ -473,7 +479,7 @@
   PairedDeviceList list = GetPairedDevices();
   EXPECT_EQ(1u, list.size());
   EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
-  EXPECT_EQ(mojom::DeviceType::kUnknown,
+  EXPECT_EQ(mojom::DeviceType::kHeadset,
             list[0]->device_properties->device_type);
 
   // Change its device type.
@@ -496,8 +502,6 @@
   PairedDeviceList list = GetPairedDevices();
   EXPECT_EQ(1u, list.size());
   EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
-  EXPECT_EQ(mojom::DeviceType::kUnknown,
-            list[0]->device_properties->device_type);
 
   ForgetDevice(paired_device_id);
   EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
@@ -515,15 +519,15 @@
   UnpairedDeviceList list = GetUnpairedDevices();
   EXPECT_EQ(1u, list.size());
   EXPECT_EQ(unpaired_device_id, list[0]->id);
-  EXPECT_EQ(mojom::DeviceType::kUnknown, list[0]->device_type);
+  EXPECT_EQ(mojom::DeviceType::kHeadset, list[0]->device_type);
 
   // Change its device type.
-  ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::PHONE);
+  ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO);
   EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
   list = GetUnpairedDevices();
   EXPECT_EQ(1u, list.size());
   EXPECT_EQ(unpaired_device_id, list[0]->id);
-  EXPECT_EQ(mojom::DeviceType::kPhone, list[0]->device_type);
+  EXPECT_EQ(mojom::DeviceType::kVideoCamera, list[0]->device_type);
 }
 
 TEST_F(DeviceCacheImplTest, UnpairedDeviceSignalStrengthChanges) {
@@ -593,5 +597,140 @@
   EXPECT_TRUE(GetUnpairedDevices().empty());
 }
 
+TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceNotReturned) {
+  Init();
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Add an unknown device. This should not be added to the unpaired list and no
+  // observers notified.
+  std::string unpaired_device_id;
+  AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
+            /*inquiry_rssi=*/1,
+            /*device_type=*/device::BluetoothDeviceType::UNKNOWN);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Update a property in the device. This should not cause any observable
+  // actions.
+  ChangeInquiryRssi(unpaired_device_id, 3);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Remove the device. This should not cause any observable actions.
+  RemoveDevice(unpaired_device_id);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+}
+
+TEST_F(DeviceCacheImplTest, UnsupportedUnpairedDeviceNotReturned) {
+  Init();
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Add a device of type PHONE. This should not be added to the unpaired list
+  // and no observers notified because this device type is unsupported.
+  std::string unpaired_device_id;
+  AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
+            /*inquiry_rssi=*/1,
+            /*device_type=*/device::BluetoothDeviceType::PHONE);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Update a property in the device. This should not cause any observable
+  // actions.
+  ChangeInquiryRssi(unpaired_device_id, 3);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Remove the device. This should not cause any observable actions.
+  RemoveDevice(unpaired_device_id);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+}
+
+TEST_F(DeviceCacheImplTest, UnknownUnpairedDeviceChangesToKnown) {
+  Init();
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Add an unknown device. This should not be added to the unpaired list and no
+  // observers notified.
+  std::string unpaired_device_id;
+  AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
+            /*inquiry_rssi=*/1,
+            /*device_type=*/device::BluetoothDeviceType::UNKNOWN);
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Update the device type to a known type. This should add the device to the
+  // unpaired list.
+  ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::VIDEO);
+  UnpairedDeviceList unpaired_list = GetUnpairedDevices();
+  EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_EQ(1u, unpaired_list.size());
+  EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id);
+
+  // Remove the device. This should notify observers.
+  RemoveDevice(unpaired_device_id);
+  EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+}
+
+TEST_F(DeviceCacheImplTest, KnownUnpairedDeviceChangesToUnknown) {
+  Init();
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Add a known device. This should notify observers.
+  std::string unpaired_device_id;
+  AddDevice(/*paired=*/false, /*connected=*/false, &unpaired_device_id,
+            /*inquiry_rssi=*/1,
+            /*device_type=*/device::BluetoothDeviceType::VIDEO);
+  UnpairedDeviceList unpaired_list = GetUnpairedDevices();
+  EXPECT_EQ(1u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_EQ(1u, unpaired_list.size());
+  EXPECT_EQ(unpaired_device_id, unpaired_list[0]->id);
+
+  // Update the device type to unknown type. This should remove the device from
+  // the unpaired list and notify observers.
+  ChangeDeviceType(unpaired_device_id, device::BluetoothDeviceType::UNKNOWN);
+  EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Remove the device. This should not cause any observable actions.
+  RemoveDevice(unpaired_device_id);
+  EXPECT_EQ(2u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+}
+
+TEST_F(DeviceCacheImplTest, UnknownPairedDeviceReturned) {
+  Init();
+  EXPECT_TRUE(GetPairedDevices().empty());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+
+  // Add an unknown paired device. This should notify observers.
+  std::string paired_device_id;
+  AddDevice(/*paired=*/true, /*connected=*/false, &paired_device_id,
+            /*inquiry_rssi=*/1,
+            /*device_type=*/device::BluetoothDeviceType::UNKNOWN);
+  EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
+  PairedDeviceList paired_list = GetPairedDevices();
+  EXPECT_EQ(1u, paired_list.size());
+  EXPECT_EQ(paired_device_id, paired_list[0]->device_properties->id);
+
+  // Update a property in the device. This should notify observers.
+  ChangeDeviceIsBlockedByPolicy(paired_device_id,
+                                /*is_blocked_by_policy=*/true);
+  EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
+  paired_list = GetPairedDevices();
+  EXPECT_EQ(1u, paired_list.size());
+  EXPECT_TRUE(paired_list[0]->device_properties->is_blocked_by_policy);
+
+  // Change the device to unpaired. This should update the paired device list
+  // but not the unpaired device list.
+  ChangePairingState(paired_device_id, /*is_now_paired=*/false);
+  EXPECT_EQ(3u, GetNumPairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetPairedDevices().empty());
+  EXPECT_EQ(0u, GetNumUnpairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetUnpairedDevices().empty());
+}
+
 }  // namespace bluetooth_config
 }  // namespace chromeos
diff --git a/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm b/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm
index d8f2ef3..b7d5900 100644
--- a/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm
+++ b/components/autofill/ios/form_util/unique_id_data_tab_helper_unittest.mm
@@ -13,15 +13,15 @@
 #error "This file requires ARC support."
 #endif
 
-// Test fixture for TabIdTabHelper class.
+// Test fixture for UniqueIDDataTabHelper class.
 class UniqueIDDataTabHelperTest : public PlatformTest {
  protected:
   web::FakeWebState first_web_state_;
   web::FakeWebState second_web_state_;
 };
 
-// Tests that a tab ID is returned for a WebState, and tab ID's are different
-// for different WebStates if they were once set differently.
+// Tests that a renderer ID is returned for a WebState, and rendered ID's are
+// different for different WebStates if they were once set differently.
 TEST_F(UniqueIDDataTabHelperTest, UniqueIdentifiers) {
   UniqueIDDataTabHelper::CreateForWebState(&first_web_state_);
   UniqueIDDataTabHelper::CreateForWebState(&second_web_state_);
@@ -51,7 +51,7 @@
   EXPECT_NE(first_available_unique_id, second_available_unique_id);
 }
 
-// Tests that a tab ID is stable across successive calls.
+// Tests that a renderer ID is stable across successive calls.
 TEST_F(UniqueIDDataTabHelperTest, StableAcrossCalls) {
   UniqueIDDataTabHelper::CreateForWebState(&first_web_state_);
   UniqueIDDataTabHelper* tab_helper =
diff --git a/components/autofill_assistant/browser/BUILD.gn b/components/autofill_assistant/browser/BUILD.gn
index e9ddc6e3..f631115 100644
--- a/components/autofill_assistant/browser/BUILD.gn
+++ b/components/autofill_assistant/browser/BUILD.gn
@@ -198,6 +198,10 @@
     "service/access_token_fetcher.h",
     "service/api_key_fetcher.cc",
     "service/api_key_fetcher.h",
+    "service/cup.cc",
+    "service/cup.h",
+    "service/cup_factory.cc",
+    "service/cup_factory.h",
     "service/cup_impl.cc",
     "service/cup_impl.h",
     "service/rpc_type.h",
@@ -434,9 +438,13 @@
     "script_tracker_unittest.cc",
     "selector_unittest.cc",
     "service/api_key_fetcher_unittest.cc",
+    "service/cup_factory_unittest.cc",
     "service/cup_impl_unittest.cc",
+    "service/cup_unittest.cc",
     "service/mock_access_token_fetcher.cc",
     "service/mock_access_token_fetcher.h",
+    "service/mock_cup.cc",
+    "service/mock_cup.h",
     "service/mock_service_request_sender.cc",
     "service/mock_service_request_sender.h",
     "service/mock_simple_url_loader_factory.cc",
diff --git a/components/autofill_assistant/browser/service/cup.cc b/components/autofill_assistant/browser/service/cup.cc
new file mode 100644
index 0000000..53508a2
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup.cc
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cup.h"
+
+#include "base/feature_list.h"
+#include "components/autofill_assistant/browser/features.h"
+
+namespace {
+
+bool ShouldSignGetActionsRequests() {
+  return base::FeatureList::IsEnabled(
+      autofill_assistant::features::kAutofillAssistantSignGetActionsRequests);
+}
+
+bool ShouldVerifyGetActionsResponses() {
+  return ShouldSignGetActionsRequests() &&
+         base::FeatureList::IsEnabled(
+             autofill_assistant::features::
+                 kAutofillAssistantVerifyGetActionsResponses);
+}
+
+}  // namespace
+
+namespace autofill_assistant {
+
+namespace cup {
+
+bool ShouldSignRequests(RpcType rpc_type) {
+  return ShouldSignGetActionsRequests() && rpc_type == RpcType::GET_ACTIONS;
+}
+
+bool ShouldVerifyResponses(RpcType rpc_type) {
+  return ShouldVerifyGetActionsResponses() && rpc_type == RpcType::GET_ACTIONS;
+}
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/cup.h b/components/autofill_assistant/browser/service/cup.h
new file mode 100644
index 0000000..eb4d4a8
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup.h
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_
+
+#include "components/autofill_assistant/browser/service/rpc_type.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace autofill_assistant {
+
+namespace cup {
+
+// Whether |PackAndSignRequest| should be called before the request is
+// submitted. Can be |false| because signing is disabled via feature flag,
+// or given message type doesn't support CUP signing.
+bool ShouldSignRequests(RpcType rpc_type);
+
+// Whether |UnpackResponse| should be called on the response from the service
+// call. Can be false because verification is disabled via feature flag or
+// |ShouldSignRequest| returns |false|.
+bool ShouldVerifyResponses(RpcType rpc_type);
+
+class CUP {
+ public:
+  virtual ~CUP() = default;
+
+  // Generates a new |request| where |original_request| is packed and signed in
+  // its |cup_data| field.
+  virtual std::string PackAndSignRequest(
+      const std::string& original_request) = 0;
+
+  // Generates a new |response| where |original_response| is unpacked from
+  // the |cup_data| field.
+  virtual absl::optional<std::string> UnpackResponse(
+      const std::string& original_response) = 0;
+
+ protected:
+  CUP() = default;
+};
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
+
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_H_
diff --git a/components/autofill_assistant/browser/service/cup_factory.cc b/components/autofill_assistant/browser/service/cup_factory.cc
new file mode 100644
index 0000000..ee91679
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup_factory.cc
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/service/cup_factory.h"
+#include "components/autofill_assistant/browser/service/cup_impl.h"
+
+namespace autofill_assistant {
+
+namespace cup {
+
+std::unique_ptr<CUP> CUPImplFactory::CreateInstance(RpcType rpc_type) const {
+  return std::make_unique<CUPImpl>(CUPImpl::CreateQuerySigner(), rpc_type);
+}
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/cup_factory.h b/components/autofill_assistant/browser/service/cup_factory.h
new file mode 100644
index 0000000..aab3b010
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup_factory.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_
+
+#include "components/autofill_assistant/browser/service/cup.h"
+
+namespace autofill_assistant {
+
+namespace cup {
+
+// Base interface for creators of CUP (Client Update Protocol) instances.
+class CUPFactory {
+ public:
+  virtual ~CUPFactory() = default;
+
+  // Creates an instance of CUP for a call of given |rpc_type|.
+  virtual std::unique_ptr<CUP> CreateInstance(RpcType rpc_type) const = 0;
+
+ protected:
+  CUPFactory() = default;
+};
+
+// Implementation of |CUPFactory| for |CUPImpl| instances.
+class CUPImplFactory : public CUPFactory {
+ public:
+  CUPImplFactory() = default;
+  ~CUPImplFactory() override = default;
+  CUPImplFactory(const CUPImplFactory&) = delete;
+  CUPImplFactory& operator=(const CUPImplFactory&) = delete;
+
+  std::unique_ptr<CUP> CreateInstance(RpcType rpc_type) const override;
+};
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
+
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_FACTORY_H_
diff --git a/components/autofill_assistant/browser/service/cup_factory_unittest.cc b/components/autofill_assistant/browser/service/cup_factory_unittest.cc
new file mode 100644
index 0000000..f7b9955
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup_factory_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/autofill_assistant/browser/service/cup_factory.h"
+#include "components/autofill_assistant/browser/service/cup_impl.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+class CUPFactoryTest : public testing::Test {
+ public:
+  CUPFactoryTest()
+      : cup_factory_{
+            std::make_unique<autofill_assistant::cup::CUPImplFactory>()} {}
+  ~CUPFactoryTest() override = default;
+
+ protected:
+  std::unique_ptr<autofill_assistant::cup::CUPFactory> cup_factory_;
+};
+
+TEST_F(CUPFactoryTest, ShouldCreateCupImplInstance) {
+  std::unique_ptr<autofill_assistant::cup::CUP> cup =
+      cup_factory_->CreateInstance(autofill_assistant::RpcType::GET_ACTIONS);
+  EXPECT_NE(cup, nullptr);
+
+  std::string packed_request = cup->PackAndSignRequest("request");
+  EXPECT_FALSE(packed_request.empty());
+}
+
+}  // namespace
diff --git a/components/autofill_assistant/browser/service/cup_impl.cc b/components/autofill_assistant/browser/service/cup_impl.cc
index e7c2f1cd..569f521 100644
--- a/components/autofill_assistant/browser/service/cup_impl.cc
+++ b/components/autofill_assistant/browser/service/cup_impl.cc
@@ -5,10 +5,9 @@
 #include "cup_impl.h"
 
 #include "base/base64.h"
-#include "base/feature_list.h"
+#include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
-#include "components/autofill_assistant/browser/features.h"
 #include "components/autofill_assistant/browser/service.pb.h"
 #include "components/client_update_protocol/ecdsa.h"
 
@@ -27,38 +26,24 @@
              : std::string();
 }
 
-bool ShouldSignGetActionsRequests() {
-  return base::FeatureList::IsEnabled(
-      autofill_assistant::features::kAutofillAssistantSignGetActionsRequests);
-}
-
-bool ShouldVerifyGetActionsResponses() {
-  return ShouldSignGetActionsRequests() &&
-         base::FeatureList::IsEnabled(
-             autofill_assistant::features::
-                 kAutofillAssistantVerifyGetActionsResponses);
-}
-
 }  // namespace
 
 namespace autofill_assistant {
 
+namespace cup {
+
 std::unique_ptr<client_update_protocol::Ecdsa> CUPImpl::CreateQuerySigner() {
   return client_update_protocol::Ecdsa::Create(kKeyVersion,
                                                GetKey(kKeyPubBytesBase64));
 }
 
-bool CUPImpl::ShouldSignRequests(RpcType rpc_type) {
-  return ShouldSignGetActionsRequests() && rpc_type == RpcType::GET_ACTIONS;
-}
-
-bool CUPImpl::ShouldVerifyResponses(RpcType rpc_type) {
-  return ShouldVerifyGetActionsResponses() && rpc_type == RpcType::GET_ACTIONS;
-}
-
-CUPImpl::CUPImpl(std::unique_ptr<client_update_protocol::Ecdsa> query_signer)
+CUPImpl::CUPImpl(std::unique_ptr<client_update_protocol::Ecdsa> query_signer,
+                 RpcType rpc_type)
     : query_signer_{std::move(query_signer)} {
   DCHECK(query_signer_);
+
+  // Only GET_ACTIONS calls have support for CUP at this moment.
+  DCHECK(rpc_type == RpcType::GET_ACTIONS);
 }
 
 CUPImpl::~CUPImpl() = default;
@@ -110,4 +95,6 @@
   return *query_signer_.get();
 }
 
+}  // namespace cup
+
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/cup_impl.h b/components/autofill_assistant/browser/service/cup_impl.h
index 106ec73..e1ecca1f 100644
--- a/components/autofill_assistant/browser/service/cup_impl.h
+++ b/components/autofill_assistant/browser/service/cup_impl.h
@@ -5,12 +5,13 @@
 #ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_
 #define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_
 
-#include "components/autofill_assistant/browser/service/rpc_type.h"
+#include "components/autofill_assistant/browser/service/cup.h"
 #include "components/client_update_protocol/ecdsa.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace autofill_assistant {
 
+namespace cup {
+
 // Implementation of the Client Update Protocol (CUP) for the service calls in
 // |autofill_assistant|.
 // https://source.chromium.org/chromium/chromium/src/+/main:docs/updater/cup.md
@@ -19,37 +20,28 @@
 // HTTP headers, and is sent as part of the request and response body instead.
 //
 // This class can only be used once per service call.
-class CUPImpl {
+class CUPImpl : public CUP {
  public:
   static std::unique_ptr<client_update_protocol::Ecdsa> CreateQuerySigner();
 
-  // Whether |PackAndSignRequest| should be called before the request is
-  // submitted. Can be |false| because signing is disabled via feature flag,
-  // or given message type doesn't support CUP signing.
-  static bool ShouldSignRequests(RpcType rpc_type);
-
-  // Whether |UnpackResponse| should be called on the response from the service
-  // call. Can be false because verification is disabled via feature flag or
-  // |ShouldSignRequest| returns |false|.
-  static bool ShouldVerifyResponses(RpcType rpc_type);
-
-  CUPImpl(std::unique_ptr<client_update_protocol::Ecdsa> query_signer);
+  CUPImpl(std::unique_ptr<client_update_protocol::Ecdsa> query_signer,
+          RpcType rpc_type);
   CUPImpl(const CUPImpl&) = delete;
   CUPImpl& operator=(const CUPImpl&) = delete;
-  ~CUPImpl();
+  ~CUPImpl() override;
 
   // Generates a new |request| where |original_request| is packed and signed in
   // its |cup_data| field.
   //
   // Should only be called if |ShouldSignRequest| returns true.
-  std::string PackAndSignRequest(const std::string& original_request);
+  std::string PackAndSignRequest(const std::string& original_request) override;
 
   // Generates a new |response| where |original_response| is unpacked from
   // the |cup_data| field.
   //
   // Should only be called if |ShouldVerifyResponse| returns true.
   absl::optional<std::string> UnpackResponse(
-      const std::string& original_response);
+      const std::string& original_response) override;
 
   // Gets the query signer object being used by this CUP instance. Needed for
   // testing.
@@ -64,6 +56,8 @@
   std::unique_ptr<client_update_protocol::Ecdsa> query_signer_;
 };
 
+}  // namespace cup
+
 }  // namespace autofill_assistant
 
 #endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_CUP_IMPL_H_
diff --git a/components/autofill_assistant/browser/service/cup_impl_unittest.cc b/components/autofill_assistant/browser/service/cup_impl_unittest.cc
index e5e6e32..1272e9af 100644
--- a/components/autofill_assistant/browser/service/cup_impl_unittest.cc
+++ b/components/autofill_assistant/browser/service/cup_impl_unittest.cc
@@ -13,92 +13,16 @@
 
 class CUPImplTest : public testing::Test {
  public:
-  CUPImplTest() : cup_{autofill_assistant::CUPImpl::CreateQuerySigner()} {}
+  CUPImplTest()
+      : cup_{autofill_assistant::cup::CUPImpl::CreateQuerySigner(),
+             autofill_assistant::RpcType::GET_ACTIONS} {}
   ~CUPImplTest() override = default;
 
  protected:
-  autofill_assistant::CUPImpl cup_;
-  base::test::ScopedFeatureList scoped_feature_list_;
-
-  void InitCupFeatures(bool enableSigning, bool enableVerifying) {
-    std::vector<base::Feature> enabled_features;
-    std::vector<base::Feature> disabled_features;
-
-    if (enableSigning) {
-      enabled_features.push_back(autofill_assistant::features::
-                                     kAutofillAssistantSignGetActionsRequests);
-    } else {
-      disabled_features.push_back(autofill_assistant::features::
-                                      kAutofillAssistantSignGetActionsRequests);
-    }
-
-    if (enableVerifying) {
-      enabled_features.push_back(
-          autofill_assistant::features::
-              kAutofillAssistantVerifyGetActionsResponses);
-    } else {
-      disabled_features.push_back(
-          autofill_assistant::features::
-              kAutofillAssistantVerifyGetActionsResponses);
-    }
-
-    scoped_feature_list_.Reset();
-    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
-  }
+  autofill_assistant::cup::CUPImpl cup_;
 };
 
-TEST_F(CUPImplTest, ShouldSignGetActionsRequestWhenFeatureActivated) {
-  InitCupFeatures(true, false);
-
-  EXPECT_TRUE(autofill_assistant::CUPImpl::ShouldSignRequests(
-      autofill_assistant::RpcType::GET_ACTIONS));
-}
-
-TEST_F(CUPImplTest, ShouldNotSignGetActionsRequestWhenFeatureNotActivated) {
-  InitCupFeatures(false, false);
-
-  EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldSignRequests(
-      autofill_assistant::RpcType::GET_ACTIONS));
-}
-
-TEST_F(CUPImplTest, ShouldNotSignNotGetActionsRequest) {
-  InitCupFeatures(true, false);
-
-  EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldSignRequests(
-      autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS));
-}
-
-TEST_F(CUPImplTest, ShouldVerifyGetActionsResponseWhenFeatureActivated) {
-  InitCupFeatures(true, true);
-
-  EXPECT_TRUE(autofill_assistant::CUPImpl::ShouldVerifyResponses(
-      autofill_assistant::RpcType::GET_ACTIONS));
-}
-
-TEST_F(CUPImplTest, ShouldNotVerifyGetActionsResponseWhenFeatureNotActivated) {
-  InitCupFeatures(true, false);
-
-  EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses(
-      autofill_assistant::RpcType::GET_ACTIONS));
-}
-
-TEST_F(CUPImplTest, ShouldNotVerifyGetActionsResponseWhenSigningNotActivated) {
-  InitCupFeatures(false, true);
-
-  EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses(
-      autofill_assistant::RpcType::GET_ACTIONS));
-}
-
-TEST_F(CUPImplTest, ShouldNotVerifyNotGetActionsResponse) {
-  InitCupFeatures(true, true);
-
-  EXPECT_FALSE(autofill_assistant::CUPImpl::ShouldVerifyResponses(
-      autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS));
-}
-
 TEST_F(CUPImplTest, PacksAndSignsGetActionsRequest) {
-  InitCupFeatures(true, false);
-
   autofill_assistant::ScriptActionRequestProto user_request;
   user_request.mutable_client_context()->set_experiment_ids("test");
   std::string user_request_str;
@@ -125,8 +49,6 @@
 }
 
 TEST_F(CUPImplTest, FailsToUnpackNonTrustedGetActionsResponse) {
-  InitCupFeatures(true, true);
-
   autofill_assistant::ScriptActionRequestProto user_request;
   user_request.mutable_client_context()->set_experiment_ids("123");
   std::string user_request_str;
diff --git a/components/autofill_assistant/browser/service/cup_unittest.cc b/components/autofill_assistant/browser/service/cup_unittest.cc
new file mode 100644
index 0000000..45aaf8d3
--- /dev/null
+++ b/components/autofill_assistant/browser/service/cup_unittest.cc
@@ -0,0 +1,98 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cup.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/autofill_assistant/browser/features.h"
+#include "components/autofill_assistant/browser/service.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace {
+
+class CUPTest : public testing::Test {
+ public:
+  CUPTest() = default;
+  ~CUPTest() override = default;
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+  void InitCupFeatures(bool enableSigning, bool enableVerifying) {
+    std::vector<base::Feature> enabled_features;
+    std::vector<base::Feature> disabled_features;
+
+    if (enableSigning) {
+      enabled_features.push_back(autofill_assistant::features::
+                                     kAutofillAssistantSignGetActionsRequests);
+    } else {
+      disabled_features.push_back(autofill_assistant::features::
+                                      kAutofillAssistantSignGetActionsRequests);
+    }
+
+    if (enableVerifying) {
+      enabled_features.push_back(
+          autofill_assistant::features::
+              kAutofillAssistantVerifyGetActionsResponses);
+    } else {
+      disabled_features.push_back(
+          autofill_assistant::features::
+              kAutofillAssistantVerifyGetActionsResponses);
+    }
+
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+};
+
+TEST_F(CUPTest, ShouldSignGetActionsRequestWhenFeatureActivated) {
+  InitCupFeatures(true, false);
+
+  EXPECT_TRUE(autofill_assistant::cup::ShouldSignRequests(
+      autofill_assistant::RpcType::GET_ACTIONS));
+}
+
+TEST_F(CUPTest, ShouldNotSignGetActionsRequestWhenFeatureNotActivated) {
+  InitCupFeatures(false, false);
+
+  EXPECT_FALSE(autofill_assistant::cup::ShouldSignRequests(
+      autofill_assistant::RpcType::GET_ACTIONS));
+}
+
+TEST_F(CUPTest, ShouldNotSignNotGetActionsRequest) {
+  InitCupFeatures(true, false);
+
+  EXPECT_FALSE(autofill_assistant::cup::ShouldSignRequests(
+      autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS));
+}
+
+TEST_F(CUPTest, ShouldVerifyGetActionsResponseWhenFeatureActivated) {
+  InitCupFeatures(true, true);
+
+  EXPECT_TRUE(autofill_assistant::cup::ShouldVerifyResponses(
+      autofill_assistant::RpcType::GET_ACTIONS));
+}
+
+TEST_F(CUPTest, ShouldNotVerifyGetActionsResponseWhenFeatureNotActivated) {
+  InitCupFeatures(true, false);
+
+  EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses(
+      autofill_assistant::RpcType::GET_ACTIONS));
+}
+
+TEST_F(CUPTest, ShouldNotVerifyGetActionsResponseWhenSigningNotActivated) {
+  InitCupFeatures(false, true);
+
+  EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses(
+      autofill_assistant::RpcType::GET_ACTIONS));
+}
+
+TEST_F(CUPTest, ShouldNotVerifyNotGetActionsResponse) {
+  InitCupFeatures(true, true);
+
+  EXPECT_FALSE(autofill_assistant::cup::ShouldVerifyResponses(
+      autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS));
+}
+
+}  // namespace
diff --git a/components/autofill_assistant/browser/service/java_service_request_sender.cc b/components/autofill_assistant/browser/service/java_service_request_sender.cc
index 7fa568de..2d601550 100644
--- a/components/autofill_assistant/browser/service/java_service_request_sender.cc
+++ b/components/autofill_assistant/browser/service/java_service_request_sender.cc
@@ -24,7 +24,8 @@
 
 void JavaServiceRequestSender::SendRequest(const GURL& url,
                                            const std::string& request_body,
-                                           ResponseCallback callback) {
+                                           ResponseCallback callback,
+                                           RpcType rpc_type) {
   DCHECK(!callback_)
       << __func__
       << " invoked while still waiting for response to previous request";
diff --git a/components/autofill_assistant/browser/service/java_service_request_sender.h b/components/autofill_assistant/browser/service/java_service_request_sender.h
index 200d17a..f1e3262 100644
--- a/components/autofill_assistant/browser/service/java_service_request_sender.h
+++ b/components/autofill_assistant/browser/service/java_service_request_sender.h
@@ -30,7 +30,8 @@
 
   void SendRequest(const GURL& url,
                    const std::string& request_body,
-                   ResponseCallback callback) override;
+                   ResponseCallback callback,
+                   RpcType rpc_type) override;
 
   void OnResponse(JNIEnv* env,
                   const base::android::JavaParamRef<jobject>& jcaller,
diff --git a/components/autofill_assistant/browser/service/mock_cup.cc b/components/autofill_assistant/browser/service/mock_cup.cc
new file mode 100644
index 0000000..713a7b0ff3
--- /dev/null
+++ b/components/autofill_assistant/browser/service/mock_cup.cc
@@ -0,0 +1,21 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "components/autofill_assistant/browser/service/mock_cup.h"
+
+namespace autofill_assistant {
+
+namespace cup {
+
+MockCUP::MockCUP() = default;
+MockCUP::~MockCUP() = default;
+
+MockCUPFactory::MockCUPFactory() = default;
+MockCUPFactory::~MockCUPFactory() = default;
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/mock_cup.h b/components/autofill_assistant/browser/service/mock_cup.h
new file mode 100644
index 0000000..36217361
--- /dev/null
+++ b/components/autofill_assistant/browser/service/mock_cup.h
@@ -0,0 +1,41 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_
+#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_
+
+#include "components/autofill_assistant/browser/service/cup.h"
+#include "components/autofill_assistant/browser/service/cup_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace autofill_assistant {
+
+namespace cup {
+
+class MockCUP : public CUP {
+ public:
+  MockCUP();
+  ~MockCUP() override;
+
+  MOCK_METHOD1(PackAndSignRequest,
+               std::string(const std::string& original_request));
+
+  MOCK_METHOD1(
+      UnpackResponse,
+      absl::optional<std::string>(const std::string& original_response));
+};
+
+class MockCUPFactory : public CUPFactory {
+ public:
+  MockCUPFactory();
+  ~MockCUPFactory() override;
+
+  MOCK_CONST_METHOD1(CreateInstance, std::unique_ptr<CUP>(RpcType rpc_type));
+};
+
+}  // namespace cup
+
+}  // namespace autofill_assistant
+
+#endif  // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_SERVICE_MOCK_CUP_H_
diff --git a/components/autofill_assistant/browser/service/mock_service_request_sender.h b/components/autofill_assistant/browser/service/mock_service_request_sender.h
index 2911f6f9..0f7d44d 100644
--- a/components/autofill_assistant/browser/service/mock_service_request_sender.h
+++ b/components/autofill_assistant/browser/service/mock_service_request_sender.h
@@ -20,14 +20,16 @@
 
   void SendRequest(const GURL& url,
                    const std::string& request_body,
-                   ResponseCallback callback) override {
-    OnSendRequest(url, request_body, callback);
+                   ResponseCallback callback,
+                   RpcType rpc_type) override {
+    OnSendRequest(url, request_body, callback, rpc_type);
   }
 
-  MOCK_METHOD3(OnSendRequest,
+  MOCK_METHOD4(OnSendRequest,
                void(const GURL& url,
                     const std::string& request_body,
-                    ResponseCallback& callback));
+                    ResponseCallback& callback,
+                    RpcType rpc_type));
 };
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/service_impl.cc b/components/autofill_assistant/browser/service/service_impl.cc
index a9b7c53..633a953 100644
--- a/components/autofill_assistant/browser/service/service_impl.cc
+++ b/components/autofill_assistant/browser/service/service_impl.cc
@@ -16,6 +16,7 @@
 #include "components/autofill_assistant/browser/features.h"
 #include "components/autofill_assistant/browser/protocol_utils.h"
 #include "components/autofill_assistant/browser/service/api_key_fetcher.h"
+#include "components/autofill_assistant/browser/service/cup_factory.h"
 #include "components/autofill_assistant/browser/service/service_request_sender_impl.h"
 #include "components/autofill_assistant/browser/switches.h"
 #include "components/autofill_assistant/browser/trigger_context.h"
@@ -44,6 +45,7 @@
     const ServerUrlFetcher& url_fetcher) {
   auto request_sender = std::make_unique<ServiceRequestSenderImpl>(
       context, client->GetAccessTokenFetcher(),
+      std::make_unique<cup::CUPImplFactory>(),
       std::make_unique<NativeURLLoaderFactory>(),
       ApiKeyFetcher().GetAPIKey(client->GetChannel()),
       /* auth_enabled = */ "false" !=
@@ -88,7 +90,7 @@
                                ProtocolUtils::CreateGetScriptsRequest(
                                    url, client_context_->AsProto(),
                                    trigger_context.GetScriptParameters()),
-                               std::move(callback));
+                               std::move(callback), RpcType::SUPPORTS_SCRIPT);
 }
 
 void ServiceImpl::GetActions(const std::string& script_path,
@@ -139,7 +141,7 @@
           script_path, url, global_payload, script_payload,
           client_context_->AsProto(), trigger_context.GetScriptParameters(),
           script_store_config_),
-      std::move(callback));
+      std::move(callback), RpcType::GET_ACTIONS);
 }
 
 void ServiceImpl::GetNextActions(
@@ -155,7 +157,7 @@
       ProtocolUtils::CreateNextScriptActionsRequest(
           previous_global_payload, previous_script_payload, processed_actions,
           timing_stats, client_context_->AsProto()),
-      std::move(callback));
+      std::move(callback), RpcType::GET_ACTIONS);
 }
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/service_impl_unittest.cc b/components/autofill_assistant/browser/service/service_impl_unittest.cc
index 267f85d9..314d6ce 100644
--- a/components/autofill_assistant/browser/service/service_impl_unittest.cc
+++ b/components/autofill_assistant/browser/service/service_impl_unittest.cc
@@ -58,8 +58,8 @@
 
 TEST_F(ServiceImplTest, GetScriptsForUrl) {
   EXPECT_CALL(*mock_client_context_, Update);
-  EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kScriptServerUrl), _, _))
+  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kScriptServerUrl), _, _,
+                                                   RpcType::SUPPORTS_SCRIPT))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
   EXPECT_CALL(mock_response_callback_,
               Run(net::HTTP_OK, std::string("response")));
@@ -74,7 +74,7 @@
       .WillOnce(RunOnceCallback<0>("token"));
   EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken("token"));
   EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kActionServerUrl), _, _))
+              OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
   EXPECT_CALL(mock_response_callback_,
               Run(net::HTTP_OK, std::string("response")));
@@ -99,7 +99,7 @@
 
   std::string get_actions_request;
   EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kActionServerUrl), _, _))
+              OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS))
       .WillOnce(SaveArg<1>(&get_actions_request));
 
   ScriptStoreConfig set_config;
@@ -131,7 +131,7 @@
   EXPECT_CALL(mock_client_, FetchPaymentsClientToken).Times(0);
   EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken).Times(0);
   EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kActionServerUrl), _, _))
+              OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
   EXPECT_CALL(mock_response_callback_,
               Run(net::HTTP_OK, std::string("response")));
@@ -152,7 +152,7 @@
   EXPECT_CALL(mock_client_, FetchPaymentsClientToken).Times(0);
   EXPECT_CALL(*mock_client_context_, SetPaymentsClientToken).Times(0);
   EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kActionServerUrl), _, _))
+              OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
   EXPECT_CALL(mock_response_callback_,
               Run(net::HTTP_OK, std::string("response")));
@@ -166,7 +166,7 @@
 TEST_F(ServiceImplTest, GetNextActions) {
   EXPECT_CALL(*mock_client_context_, Update);
   EXPECT_CALL(*mock_request_sender_,
-              OnSendRequest(GURL(kActionServerUrl), _, _))
+              OnSendRequest(GURL(kActionServerUrl), _, _, RpcType::GET_ACTIONS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string("response")));
   EXPECT_CALL(mock_response_callback_,
               Run(net::HTTP_OK, std::string("response")));
diff --git a/components/autofill_assistant/browser/service/service_request_sender.h b/components/autofill_assistant/browser/service/service_request_sender.h
index 865e9dd..f50790d2 100644
--- a/components/autofill_assistant/browser/service/service_request_sender.h
+++ b/components/autofill_assistant/browser/service/service_request_sender.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "components/autofill_assistant/browser/service/rpc_type.h"
 #include "url/gurl.h"
 
 namespace autofill_assistant {
@@ -24,7 +25,8 @@
   // response itself.
   virtual void SendRequest(const GURL& url,
                            const std::string& request_body,
-                           ResponseCallback callback) = 0;
+                           ResponseCallback response_callback,
+                           RpcType rpc_type) = 0;
 };
 
 }  // namespace autofill_assistant
diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl.cc b/components/autofill_assistant/browser/service/service_request_sender_impl.cc
index 7bde65e..b3d19e2 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_impl.cc
+++ b/components/autofill_assistant/browser/service/service_request_sender_impl.cc
@@ -4,7 +4,12 @@
 
 #include "components/autofill_assistant/browser/service/service_request_sender_impl.h"
 
+#include "base/feature_list.h"
 #include "base/strings/strcat.h"
+#include "components/autofill_assistant/browser/features.h"
+#include "components/autofill_assistant/browser/service.pb.h"
+#include "components/autofill_assistant/browser/service/cup.h"
+#include "components/autofill_assistant/browser/service/cup_impl.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
 #include "net/base/load_flags.h"
@@ -115,6 +120,20 @@
                   loader_factory, std::move(callback));
 }
 
+void VerifyCupResponse(
+    std::unique_ptr<autofill_assistant::cup::CUP> cup,
+    autofill_assistant::ServiceRequestSender::ResponseCallback callback,
+    int http_status,
+    const std::string& response) {
+  absl::optional<std::string> unpacked_response = cup->UnpackResponse(response);
+  if (!unpacked_response) {
+    LOG(ERROR) << "Failed to unpack or verify a response.";
+    return std::move(callback).Run(net::HTTP_UNAUTHORIZED, std::string());
+  }
+
+  return std::move(callback).Run(http_status, *unpacked_response);
+}
+
 }  // namespace
 
 namespace autofill_assistant {
@@ -122,12 +141,14 @@
 ServiceRequestSenderImpl::ServiceRequestSenderImpl(
     content::BrowserContext* context,
     AccessTokenFetcher* access_token_fetcher,
+    std::unique_ptr<cup::CUPFactory> cup_factory,
     std::unique_ptr<SimpleURLLoaderFactory> loader_factory,
     const std::string& api_key,
     bool auth_enabled,
     bool disable_auth_if_no_access_token)
     : context_(context),
       access_token_fetcher_(access_token_fetcher),
+      cup_factory_(std::move(cup_factory)),
       loader_factory_(std::move(loader_factory)),
       api_key_(api_key),
       auth_enabled_(auth_enabled),
@@ -139,7 +160,30 @@
 
 void ServiceRequestSenderImpl::SendRequest(const GURL& url,
                                            const std::string& request_body,
-                                           ResponseCallback callback) {
+                                           ResponseCallback callback,
+                                           RpcType rpc_type) {
+  if (!cup::ShouldSignRequests(rpc_type)) {
+    InternalSendRequest(url, request_body, std::move(callback));
+    return;
+  }
+
+  std::unique_ptr<cup::CUP> cup =
+      cup_factory_->CreateInstance(RpcType::GET_ACTIONS);
+  std::string signed_request = cup->PackAndSignRequest(request_body);
+
+  auto maybe_wrapped_callback = std::move(callback);
+  if (cup::ShouldVerifyResponses(rpc_type)) {
+    maybe_wrapped_callback = base::BindOnce(&VerifyCupResponse, std::move(cup),
+                                            std::move(maybe_wrapped_callback));
+  }
+
+  InternalSendRequest(url, signed_request, std::move(maybe_wrapped_callback));
+}
+
+void ServiceRequestSenderImpl::InternalSendRequest(
+    const GURL& url,
+    const std::string& request_body,
+    ResponseCallback callback) {
   if (auth_enabled_ && access_token_fetcher_ == nullptr) {
     LOG(ERROR) << "auth requested, but no access token fetcher provided";
     std::move(callback).Run(net::HTTP_UNAUTHORIZED, std::string());
@@ -220,7 +264,7 @@
     DCHECK(!retried_with_fresh_access_token_);
     retried_with_fresh_access_token_ = true;
     access_token_fetcher_->InvalidateAccessToken(access_token);
-    SendRequest(url, request_body, std::move(callback));
+    InternalSendRequest(url, request_body, std::move(callback));
     return;
   }
   std::move(callback).Run(http_status, response);
diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl.h b/components/autofill_assistant/browser/service/service_request_sender_impl.h
index 4d2b305..3fd743d 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_impl.h
+++ b/components/autofill_assistant/browser/service/service_request_sender_impl.h
@@ -12,6 +12,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "components/autofill_assistant/browser/service/access_token_fetcher.h"
+#include "components/autofill_assistant/browser/service/cup_factory.h"
 #include "components/autofill_assistant/browser/service/service_request_sender.h"
 #include "components/autofill_assistant/browser/service/simple_url_loader_factory.h"
 #include "content/public/browser/browser_context.h"
@@ -33,6 +34,7 @@
   ServiceRequestSenderImpl(
       content::BrowserContext* context,
       AccessTokenFetcher* access_token_fetcher,
+      std::unique_ptr<cup::CUPFactory> cup_factory,
       std::unique_ptr<SimpleURLLoaderFactory> loader_factory,
       const std::string& api_key,
       bool auth_enabled,
@@ -42,18 +44,27 @@
   ServiceRequestSenderImpl& operator=(const ServiceRequestSenderImpl&) = delete;
 
   // Sends |request_body| to |url|. Depending on configuration, the request
-  // will be authenticated either with an Oauth access token or the api key.
-  // Returns the http status code and the response itself. If the returned http
-  // headers could not be parsed, the http code will be 0.
+  // will be authenticated either with an Oauth access token or the api key. The
+  // |rpc_type| will be used to decide whether to use CUP verification. Returns
+  // the http status code and the response itself. If the returned http headers
+  // could not be parsed, the http code will be 0.
   //
   // When an auth-request first fails with a 401, the access token is
   // invalidated and fetched again. If the request fails again, the request
   // is considered failed and the callback is invoked.
   void SendRequest(const GURL& url,
                    const std::string& request_body,
-                   ResponseCallback callback) override;
+                   ResponseCallback callback,
+                   RpcType rpc_type) override;
 
  private:
+  // Unlike |ServiceRequestSenderImpl::SendRequest|, assumes that any necessary
+  // CUP signing and validation is already done or accounted for in the
+  // |callback|.
+  void InternalSendRequest(const GURL& url,
+                           const std::string& request_body,
+                           ResponseCallback callback);
+
   void SendRequestAuth(const GURL& url,
                        const std::string& request_body,
                        const std::string& access_token,
@@ -74,6 +85,7 @@
 
   raw_ptr<content::BrowserContext> context_ = nullptr;
   raw_ptr<AccessTokenFetcher> access_token_fetcher_ = nullptr;
+  std::unique_ptr<cup::CUPFactory> cup_factory_;
   std::unique_ptr<SimpleURLLoaderFactory> loader_factory_;
 
   // API key to add to the URL of unauthenticated requests.
diff --git a/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc b/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc
index f2586ec..896e175 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc
+++ b/components/autofill_assistant/browser/service/service_request_sender_impl_unittest.cc
@@ -11,8 +11,12 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/autofill_assistant/browser/features.h"
+#include "components/autofill_assistant/browser/service.pb.h"
 #include "components/autofill_assistant/browser/service/access_token_fetcher.h"
 #include "components/autofill_assistant/browser/service/mock_access_token_fetcher.h"
+#include "components/autofill_assistant/browser/service/mock_cup.h"
 #include "components/autofill_assistant/browser/service/mock_simple_url_loader_factory.h"
 #include "components/autofill_assistant/browser/service/mock_url_loader.h"
 #include "content/public/test/browser_task_environment.h"
@@ -51,6 +55,7 @@
   ~ServiceRequestSenderImplTest() override = default;
 
  protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
   base::MockCallback<base::OnceCallback<void(int, const std::string&)>>
       mock_response_callback_;
   // Note: |task_environment_| must be created before |context_|, else creation
@@ -58,9 +63,37 @@
   content::BrowserTaskEnvironment task_environment_;
   content::TestBrowserContext context_;
   NiceMock<MockAccessTokenFetcher> mock_access_token_fetcher_;
+
+  void InitCupFeatures(bool enableSigning, bool enableVerifying) {
+    std::vector<base::Feature> enabled_features;
+    std::vector<base::Feature> disabled_features;
+
+    if (enableSigning) {
+      enabled_features.push_back(autofill_assistant::features::
+                                     kAutofillAssistantSignGetActionsRequests);
+    } else {
+      disabled_features.push_back(autofill_assistant::features::
+                                      kAutofillAssistantSignGetActionsRequests);
+    }
+
+    if (enableVerifying) {
+      enabled_features.push_back(
+          autofill_assistant::features::
+              kAutofillAssistantVerifyGetActionsResponses);
+    } else {
+      disabled_features.push_back(
+          autofill_assistant::features::
+              kAutofillAssistantVerifyGetActionsResponses);
+    }
+
+    scoped_feature_list_.Reset();
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
 };
 
 TEST_F(ServiceRequestSenderImplTest, SendUnauthenticatedRequest) {
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
   auto loader_factory =
       std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
   auto loader = std::make_unique<NiceMock<MockURLLoader>>();
@@ -88,16 +121,19 @@
   ServiceRequestSenderImpl request_sender{
       &context_,
       /* access_token_fetcher = */ nullptr,
+      std::move(cup_factory),
       std::move(loader_factory),
       std::string("fake_api_key"),
       /* auth_enabled = */ false,
       /* disable_auth_if_no_access_token = */ true};
-  request_sender.SendRequest(GURL("https://www.example.com"),
-                             std::string("request"),
-                             mock_response_callback_.Get());
+  request_sender.SendRequest(
+      GURL("https://www.example.com"), std::string("request"),
+      mock_response_callback_.Get(), RpcType::GET_TRIGGER_SCRIPTS);
 }
 
 TEST_F(ServiceRequestSenderImplTest, SendAuthenticatedRequest) {
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
   auto loader_factory =
       std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
   auto loader = std::make_unique<NiceMock<MockURLLoader>>();
@@ -131,13 +167,15 @@
   ServiceRequestSenderImpl request_sender{
       &context_,
       /* access_token_fetcher = */ &mock_access_token_fetcher_,
+      std::move(cup_factory),
       std::move(loader_factory),
       /* api_key = */ std::string(""),
       /* auth_enabled = */ true,
       /* disable_auth_if_no_access_token = */ true};
   request_sender.SendRequest(GURL("https://www.example.com"),
                              std::string("request"),
-                             mock_response_callback_.Get());
+                             mock_response_callback_.Get(),
+                             autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS);
 }
 
 TEST_F(ServiceRequestSenderImplTest,
@@ -146,6 +184,8 @@
       .Times(1)
       .WillOnce(RunOnceCallback<0>(true, /*access_token = */ ""));
 
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
   auto loader_factory =
       std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
   auto loader = std::make_unique<NiceMock<MockURLLoader>>();
@@ -173,13 +213,15 @@
   ServiceRequestSenderImpl request_sender{
       &context_,
       /* access_token_fetcher = */ &mock_access_token_fetcher_,
+      std::move(cup_factory),
       std::move(loader_factory),
       /* api_key = */ std::string("fake_api_key"),
       /* auth_enabled = */ true,
       /* disable_auth_if_no_access_token = */ true};
   request_sender.SendRequest(GURL("https://www.example.com"),
                              std::string("request"),
-                             mock_response_callback_.Get());
+                             mock_response_callback_.Get(),
+                             autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS);
 }
 
 TEST_F(ServiceRequestSenderImplTest,
@@ -189,6 +231,8 @@
       .WillOnce(
           RunOnceCallback<0>(/*success = */ false, /*access_token = */ ""));
 
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
   auto loader_factory =
       std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
   auto loader = std::make_unique<NiceMock<MockURLLoader>>();
@@ -216,13 +260,159 @@
   ServiceRequestSenderImpl request_sender{
       &context_,
       /* access_token_fetcher = */ &mock_access_token_fetcher_,
+      std::move(cup_factory),
       std::move(loader_factory),
       /* api_key = */ std::string("fake_api_key"),
       /* auth_enabled = */ true,
       /* disable_auth_if_no_access_token = */ true};
   request_sender.SendRequest(GURL("https://www.example.com"),
                              std::string("request"),
-                             mock_response_callback_.Get());
+                             mock_response_callback_.Get(),
+                             autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS);
+}
+
+TEST_F(ServiceRequestSenderImplTest,
+       DoesNotCreateInstanceWhenFeatureNotEnabled) {
+  InitCupFeatures(false, false);
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
+  auto loader_factory =
+      std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
+  auto loader = std::make_unique<NiceMock<MockURLLoader>>();
+  auto response_info = CreateResponseInfo(net::HTTP_OK, "OK");
+  EXPECT_CALL(*loader_factory, OnCreateLoader(_, _))
+      .WillOnce([&](::network::ResourceRequest* resource_request,
+                    const ::net::NetworkTrafficAnnotationTag& annotation_tag) {
+        EXPECT_FALSE(resource_request->headers.HasHeader("Authorization"));
+        EXPECT_EQ(resource_request->url,
+                  GURL("https://www.example.com/?key=fake_api_key"));
+        return std::move(loader);
+      });
+  EXPECT_CALL(*loader,
+              AttachStringForUpload(std::string("request"),
+                                    std::string("application/x-protobuffer")))
+      .Times(1);
+  EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _))
+      .WillOnce(WithArgs<1>([&](auto&& callback) {
+        std::move(callback).Run(std::make_unique<std::string>("response"));
+      }));
+  EXPECT_CALL(*loader, ResponseInfo)
+      .WillRepeatedly(Return(response_info.get()));
+  EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response"));
+
+  EXPECT_CALL(*cup_factory, CreateInstance(_)).Times(0);
+  ServiceRequestSenderImpl request_sender{
+      &context_,
+      /* access_token_fetcher = */ nullptr,
+      std::move(cup_factory),
+      std::move(loader_factory),
+      std::string("fake_api_key"),
+      /* auth_enabled = */ false,
+      /* disable_auth_if_no_access_token = */ true};
+  request_sender.SendRequest(
+      GURL("https://www.example.com"), std::string("request"),
+      mock_response_callback_.Get(), RpcType::GET_ACTIONS);
+}
+
+TEST_F(ServiceRequestSenderImplTest, SignsGetActionsRequestWhenFeatureEnabled) {
+  InitCupFeatures(true, false);
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
+  auto cup = std::make_unique<NiceMock<autofill_assistant::cup::MockCUP>>();
+  auto loader_factory =
+      std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
+  auto loader = std::make_unique<NiceMock<MockURLLoader>>();
+  auto response_info = CreateResponseInfo(net::HTTP_OK, "OK");
+  EXPECT_CALL(*loader_factory, OnCreateLoader(_, _))
+      .WillOnce([&](::network::ResourceRequest* resource_request,
+                    const ::net::NetworkTrafficAnnotationTag& annotation_tag) {
+        EXPECT_FALSE(resource_request->headers.HasHeader("Authorization"));
+        EXPECT_EQ(resource_request->url,
+                  GURL("https://www.example.com/?key=fake_api_key"));
+        return std::move(loader);
+      });
+  EXPECT_CALL(*loader,
+              AttachStringForUpload(std::string("signed_request"),
+                                    std::string("application/x-protobuffer")))
+      .Times(1);
+  EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _))
+      .WillOnce(WithArgs<1>([&](auto&& callback) {
+        std::move(callback).Run(std::make_unique<std::string>("response"));
+      }));
+  EXPECT_CALL(*loader, ResponseInfo)
+      .WillRepeatedly(Return(response_info.get()));
+  EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response"));
+
+  EXPECT_CALL(*cup_factory,
+              CreateInstance(autofill_assistant::RpcType::GET_ACTIONS))
+      .WillOnce([&]() { return std::move(cup); });
+  EXPECT_CALL(*cup, PackAndSignRequest("request")).WillOnce([&]() {
+    return "signed_request";
+  });
+  EXPECT_CALL(*cup, UnpackResponse(_)).Times(0);
+  ServiceRequestSenderImpl request_sender{
+      &context_,
+      /* access_token_fetcher = */ nullptr,
+      std::move(cup_factory),
+      std::move(loader_factory),
+      std::string("fake_api_key"),
+      /* auth_enabled = */ false,
+      /* disable_auth_if_no_access_token = */ true};
+  request_sender.SendRequest(
+      GURL("https://www.example.com"), std::string("request"),
+      mock_response_callback_.Get(), RpcType::GET_ACTIONS);
+}
+
+TEST_F(ServiceRequestSenderImplTest, ValidatesGetActionsResponsesWhenEnabled) {
+  InitCupFeatures(true, true);
+  auto cup_factory =
+      std::make_unique<NiceMock<autofill_assistant::cup::MockCUPFactory>>();
+  auto cup = std::make_unique<NiceMock<autofill_assistant::cup::MockCUP>>();
+  auto loader_factory =
+      std::make_unique<NiceMock<MockSimpleURLLoaderFactory>>();
+  auto loader = std::make_unique<NiceMock<MockURLLoader>>();
+  auto response_info = CreateResponseInfo(net::HTTP_OK, "OK");
+  EXPECT_CALL(*loader_factory, OnCreateLoader(_, _))
+      .WillOnce([&](::network::ResourceRequest* resource_request,
+                    const ::net::NetworkTrafficAnnotationTag& annotation_tag) {
+        EXPECT_FALSE(resource_request->headers.HasHeader("Authorization"));
+        EXPECT_EQ(resource_request->url,
+                  GURL("https://www.example.com/?key=fake_api_key"));
+        return std::move(loader);
+      });
+  EXPECT_CALL(*loader,
+              AttachStringForUpload(std::string("signed_request"),
+                                    std::string("application/x-protobuffer")))
+      .Times(1);
+  EXPECT_CALL(*loader, DownloadToStringOfUnboundedSizeUntilCrashAndDie(_, _))
+      .WillOnce(WithArgs<1>([&](auto&& callback) {
+        std::move(callback).Run(
+            std::make_unique<std::string>("packed_response"));
+      }));
+  EXPECT_CALL(*loader, ResponseInfo)
+      .WillRepeatedly(Return(response_info.get()));
+  EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response"));
+
+  EXPECT_CALL(*cup_factory,
+              CreateInstance(autofill_assistant::RpcType::GET_ACTIONS))
+      .WillOnce([&]() { return std::move(cup); });
+  EXPECT_CALL(*cup, PackAndSignRequest("request")).WillOnce([&]() {
+    return "signed_request";
+  });
+  EXPECT_CALL(*cup, UnpackResponse("packed_response")).WillOnce([&]() {
+    return "response";
+  });
+  ServiceRequestSenderImpl request_sender{
+      &context_,
+      /* access_token_fetcher = */ nullptr,
+      std::move(cup_factory),
+      std::move(loader_factory),
+      std::string("fake_api_key"),
+      /* auth_enabled = */ false,
+      /* disable_auth_if_no_access_token = */ true};
+  request_sender.SendRequest(
+      GURL("https://www.example.com"), std::string("request"),
+      mock_response_callback_.Get(), autofill_assistant::RpcType::GET_ACTIONS);
 }
 
 // TODO(b/170934170): Add tests for full unit test coverage of
diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc b/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc
index 8c4b7a1..707ea7b 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc
+++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl.cc
@@ -15,7 +15,8 @@
 
 void ServiceRequestSenderLocalImpl::SendRequest(const GURL& url,
                                                 const std::string& request_body,
-                                                ResponseCallback callback) {
+                                                ResponseCallback callback,
+                                                RpcType rpc_type) {
   std::move(callback).Run(net::HTTP_OK, response_);
 }
 
diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl.h b/components/autofill_assistant/browser/service/service_request_sender_local_impl.h
index a7cd8fd..ff360395 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_local_impl.h
+++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl.h
@@ -22,7 +22,8 @@
   // TODO(arbesser): Make this more flexible.
   void SendRequest(const GURL& url,
                    const std::string& request_body,
-                   ResponseCallback callback) override;
+                   ResponseCallback callback,
+                   RpcType rpc_type) override;
 
  private:
   std::string response_;
diff --git a/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc b/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc
index 3f36931..ba85aaf 100644
--- a/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc
+++ b/components/autofill_assistant/browser/service/service_request_sender_local_impl_unittest.cc
@@ -28,10 +28,10 @@
 TEST_F(ServiceRequestSenderLocalImplTest, SendRequestAlwaysReturnsResponse) {
   ServiceRequestSenderLocalImpl service_request_sender = {"response"};
   EXPECT_CALL(mock_response_callback_, Run(net::HTTP_OK, "response")).Times(2);
-  service_request_sender.SendRequest(GURL(), "request_1",
-                                     mock_response_callback_.Get());
-  service_request_sender.SendRequest(GURL(), "request_2",
-                                     mock_response_callback_.Get());
+  service_request_sender.SendRequest(
+      GURL(), "request_1", mock_response_callback_.Get(), RpcType::UNKNOWN);
+  service_request_sender.SendRequest(
+      GURL(), "request_2", mock_response_callback_.Get(), RpcType::UNKNOWN);
 }
 
 }  // namespace
diff --git a/components/autofill_assistant/browser/starter.cc b/components/autofill_assistant/browser/starter.cc
index d573cc68..2bb1c9aab 100644
--- a/components/autofill_assistant/browser/starter.cc
+++ b/components/autofill_assistant/browser/starter.cc
@@ -19,6 +19,7 @@
 #include "components/autofill_assistant/browser/features.h"
 #include "components/autofill_assistant/browser/intent_strings.h"
 #include "components/autofill_assistant/browser/service/api_key_fetcher.h"
+#include "components/autofill_assistant/browser/service/cup_impl.h"
 #include "components/autofill_assistant/browser/service/server_url_fetcher.h"
 #include "components/autofill_assistant/browser/service/service_request_sender.h"
 #include "components/autofill_assistant/browser/service/service_request_sender_impl.h"
@@ -78,6 +79,7 @@
   return std::make_unique<ServiceRequestSenderImpl>(
       browser_context,
       /* access_token_fetcher = */ nullptr,
+      std::make_unique<cup::CUPImplFactory>(),
       std::make_unique<NativeURLLoaderFactory>(),
       ApiKeyFetcher().GetAPIKey(delegate->GetChannel()),
       /* auth_enabled = */ false,
diff --git a/components/autofill_assistant/browser/starter_unittest.cc b/components/autofill_assistant/browser/starter_unittest.cc
index 95051d5..b1c2dc8 100644
--- a/components/autofill_assistant/browser/starter_unittest.cc
+++ b/components/autofill_assistant/browser/starter_unittest.cc
@@ -657,9 +657,10 @@
         trigger_script_coordinator_->PerformTriggerScriptAction(
             TriggerScriptProto::ACCEPT);
       });
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(
           WithArgs<1, 2>([&](const std::string& request_body,
                              ServiceRequestSender::ResponseCallback& callback) {
@@ -1076,9 +1077,10 @@
       features::kAutofillAssistantInCCTTriggering);
   starter_->CheckSettings();
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(
           WithArgs<1, 2>([&](const std::string& request_body,
                              ServiceRequestSender::ResponseCallback& callback) {
@@ -1183,9 +1185,10 @@
       .Times(0);
   SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK,
                                    CreateTriggerScriptResponseForTest()));
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(1);
@@ -1422,9 +1425,10 @@
       features::kAutofillAssistantInCCTTriggering);
   starter_->CheckSettings();
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, std::string()));
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(0);
   EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
@@ -1479,9 +1483,10 @@
       features::kAutofillAssistantInCCTTriggering);
   starter_->CheckSettings();
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK,
                                    CreateTriggerScriptResponseForTest()));
   EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
@@ -1540,9 +1545,10 @@
       features::kAutofillAssistantInCCTTriggering);
   starter_->CheckSettings();
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(
           WithArg<2>([&](ServiceRequestSender::ResponseCallback& callback) {
             // Empty response == no trigger scripts available.
@@ -2017,9 +2023,10 @@
                                        mock_runtime_manager_.GetWeakPtr(),
                                        task_environment()->GetMockTickClock());
 
-  EXPECT_CALL(*mock_trigger_script_service_request_sender_,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *mock_trigger_script_service_request_sender_,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(WithArg<1>([&](const std::string& request_body) {
         GetTriggerScriptsRequestProto request;
         ASSERT_TRUE(request.ParseFromString(request_body));
@@ -2086,9 +2093,10 @@
   fake_platform_delegate.trigger_script_request_sender_for_test_ =
       std::move(service_request_sender);
 
-  EXPECT_CALL(*service_request_sender_ptr,
-              OnSendRequest(
-                  GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
+  EXPECT_CALL(
+      *service_request_sender_ptr,
+      OnSendRequest(GURL("https://automate-pa.googleapis.com/v1/triggers"), _,
+                    _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(WithArg<1>([&](const std::string& request_body) {
         GetTriggerScriptsRequestProto request;
         ASSERT_TRUE(request.ParseFromString(request_body));
diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
index 7244422..d32c05ea 100644
--- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
+++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.cc
@@ -78,7 +78,8 @@
           deeplink_url_, client_context,
           trigger_context_->GetScriptParameters()),
       base::BindOnce(&TriggerScriptCoordinator::OnGetTriggerScripts,
-                     weak_ptr_factory_.GetWeakPtr()));
+                     weak_ptr_factory_.GetWeakPtr()),
+      autofill_assistant::RpcType::GET_TRIGGER_SCRIPTS);
 }
 
 void TriggerScriptCoordinator::OnGetTriggerScripts(
diff --git a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
index fd89a25d..25a9dcde 100644
--- a/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
+++ b/components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator_unittest.cc
@@ -175,9 +175,12 @@
       {"FALLBACK_BUNDLE_ID", "fallback_id"},
       {"FALLBACK_BUNDLE_VERSION", "fallback_version"}};
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce([&](const GURL& url, const std::string& request_body,
-                    ServiceRequestSender::ResponseCallback& callback) {
+                    ServiceRequestSender::ResponseCallback& callback,
+                    RpcType rpc_type) {
         GetTriggerScriptsRequestProto request;
         ASSERT_TRUE(request.ParseFromString(request_body));
         EXPECT_THAT(request.url(), Eq(kFakeDeepLink));
@@ -210,7 +213,9 @@
 }
 
 TEST_F(TriggerScriptCoordinatorTest, StopOnBackendRequestFailed) {
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, ""));
   EXPECT_CALL(
       mock_callback_,
@@ -225,7 +230,9 @@
 }
 
 TEST_F(TriggerScriptCoordinatorTest, StopOnParsingError) {
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, "invalid"));
   EXPECT_CALL(
       mock_callback_,
@@ -241,7 +248,9 @@
 }
 
 TEST_F(TriggerScriptCoordinatorTest, StopOnNoTriggerScriptsAvailable) {
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, ""));
   EXPECT_CALL(
       mock_callback_,
@@ -268,7 +277,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_, ClearConditions).Times(1);
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
@@ -296,7 +307,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -332,7 +345,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
@@ -354,7 +369,9 @@
 
   // When a hidden tab becomes visible again, the trigger scripts must be
   // fetched again.
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -373,7 +390,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -413,7 +432,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -447,7 +468,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -482,7 +505,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -506,7 +531,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -553,7 +580,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -575,7 +604,9 @@
   SimulateNavigateToUrl(GURL("https://example.different.com"));
   SimulateNavigateToUrl(GURL("https://also-not-supported.com"));
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, /* response = */ ""));
   // However, when the tab becomes visible again, the trigger script is
   // restarted and thus fails if the tab is still on an unsupported domain.
@@ -600,7 +631,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -639,7 +672,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   // Note: expect 4 calls: 1 initial plus 3 until timeout.
@@ -679,7 +714,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -725,7 +762,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -751,7 +790,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -801,7 +842,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -830,7 +873,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -859,7 +904,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -887,7 +934,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -913,7 +962,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -946,7 +997,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -968,7 +1021,9 @@
 
   // When a non-interactable tab becomes interactable again, the trigger scripts
   // must be fetched again.
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -987,7 +1042,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -1028,7 +1085,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -1094,7 +1153,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -1135,7 +1196,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   EXPECT_CALL(*mock_dynamic_trigger_conditions_,
               OnUpdate(mock_web_controller_.get(), _))
@@ -1173,7 +1236,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
   ON_CALL(*mock_dynamic_trigger_conditions_,
           OnUpdate(mock_web_controller_.get(), _))
@@ -1213,7 +1278,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   coordinator_->Start(
@@ -1238,7 +1305,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -1292,7 +1361,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -1331,7 +1402,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -1357,7 +1430,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -1389,7 +1464,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   ON_CALL(*mock_dynamic_trigger_conditions_,
@@ -1418,7 +1495,9 @@
 }
 
 TEST_F(TriggerScriptCoordinatorTest, StoppingTwiceDoesNotCrash) {
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, ""));
   EXPECT_CALL(*mock_ui_delegate_, Detach).Times(2);
   EXPECT_CALL(*mock_ui_delegate_, HideTriggerScript).Times(0);
@@ -1444,7 +1523,9 @@
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
 
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   EXPECT_CALL(*mock_dynamic_trigger_conditions_, OnUpdate)
@@ -1479,7 +1560,9 @@
   response.add_trigger_scripts();
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   coordinator_->Start(GURL(kFakeDeepLink), std::make_unique<TriggerContext>(),
@@ -1524,7 +1607,9 @@
   response.add_trigger_scripts();
   std::string serialized_response;
   response.SerializeToString(&serialized_response);
-  EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
+  EXPECT_CALL(
+      *mock_request_sender_,
+      OnSendRequest(GURL(kFakeServerUrl), _, _, RpcType::GET_TRIGGER_SCRIPTS))
       .WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
 
   EXPECT_CALL(mock_callback_, Run).Times(0);
diff --git a/components/browser_ui/styles/android/java/res/values/themes.xml b/components/browser_ui/styles/android/java/res/values/themes.xml
index 53b0292..f3c3995 100644
--- a/components/browser_ui/styles/android/java/res/values/themes.xml
+++ b/components/browser_ui/styles/android/java/res/values/themes.xml
@@ -4,6 +4,7 @@
      found in the LICENSE file. -->
 
 <resources>
+    <!-- Colors should be mirrored by Base.Theme.BrowserUI.DialogWhenLarge. -->
     <style name="Base.V21.Theme.BrowserUI" parent="Theme.MaterialComponents.DayNight.NoActionBar">
         <!-- Color palettes -->
         <item name="colorPrimary">@color/baseline_primary_600</item>
@@ -61,10 +62,69 @@
         <item name="default_icon_color_disabled">@color/default_icon_color_disabled_dark</item>
         <item name="default_icon_color_disabled_inverse">@color/default_icon_color_disabled_light</item>
     </style>
-
     <style name="Base.V31.Theme.BrowserUI" parent="Base.V21.Theme.BrowserUI" />
     <style name="Base.Theme.BrowserUI" parent="Base.V31.Theme.BrowserUI" />
 
+    <!-- Colors should be mirrored by Base.V21.Theme.BrowserUI. -->
+    <style name="Base.Theme.BrowserUI.DialogWhenLarge" parent="Theme.MaterialComponents.DayNight.DialogWhenLarge">
+         <!-- Color palettes -->
+        <item name="colorPrimary">@color/baseline_primary_600</item>
+        <item name="colorPrimaryDark">@android:color/black</item>
+        <item name="colorOnPrimary">@color/baseline_primary_0</item>
+        <item name="colorPrimaryContainer">@color/baseline_primary_100</item>
+        <item name="colorOnPrimaryContainer">@color/baseline_primary_900</item>
+        <item name="colorAccent">@macro/default_control_color_active</item>
+        <item name="android:colorBackground">@color/baseline_neutral_0</item>
+        <item name="colorOnBackground">@color/baseline_neutral_900</item>
+        <item name="colorSurface">@color/baseline_neutral_0</item>
+        <item name="colorOnSurface">@color/baseline_neutral_900</item>
+        <item name="colorSurfaceVariant">@color/baseline_neutral_variant_100</item>
+        <item name="colorOnSurfaceVariant">@color/baseline_neutral_variant_700</item>
+        <item name="colorOnSurfaceInverse">@color/baseline_neutral_50</item>
+        <item name="colorOutline">@color/baseline_neutral_variant_500</item>
+        <item name="colorError">@color/baseline_error_600</item>
+
+        <!-- Text colors-->
+        <item name="android:textColorPrimary">@color/default_text_color_list</item>
+        <item name="android:textColorSecondary">@color/default_text_color_secondary_list</item>
+        <item name="android:textColorLink">@color/default_text_color_link</item>
+        <item name="android:textColorHighlight">@color/text_highlight_color</item>
+        <item name="android:textColorHint">@color/default_text_color_hint_list</item>
+
+        <!-- Widget colors: checkboxes, switches, buttons, etc. -->
+        <item name="colorControlNormal">@macro/default_control_color_normal</item>
+        <item name="colorControlActivated">@macro/default_control_color_active</item>
+        <item name="colorControlHighlight">@color/control_highlight_color</item>
+
+        <!-- Elevation overlays -->
+        <item name="elevationOverlayEnabled">true</item>
+        <item name="elevationOverlayColor">@color/baseline_neutral_600</item>
+        <item name="elevationOverlayAccentColor">?attr/colorPrimary</item>
+
+        <!-- Custom roles -->
+        <!-- Dynamic roles -->
+        <item name="colorSwitchThumbNormal">?attr/colorSurface</item>
+        <item name="colorSwitchThumbDisabled">?attr/colorSurface</item>
+        <item name="colorSwitchTrackNormal">?attr/colorSurfaceVariant</item>
+        <!-- Non-dynamic roles for switches -->
+        <item name="colorPrimaryNonDynamic">@color/baseline_primary_600</item>
+        <item name="colorPrimaryContainerNonDynamic">@color/baseline_primary_100</item>
+        <item name="colorSwitchThumbDisabledNonDynamic">@color/baseline_neutral_0</item>
+        <item name="colorSwitchThumbNormalNonDynamic">@color/baseline_neutral_0</item>
+        <item name="colorSwitchTrackNormalNonDynamic">@color/baseline_neutral_variant_100</item>
+
+        <!-- Custom semantic names -->
+        <!-- Supports dynamic colors now. -->
+        <item name="default_bg_color_dynamic">?attr/colorSurface</item>
+        <item name="divider_line_bg_color_dynamic">?attr/colorSurfaceVariant</item>
+        <!-- Common icon colors for drawables. -->
+        <item name="default_icon_color_inverse">@color/default_icon_color_light</item>
+        <item name="default_icon_color_secondary">@color/default_icon_color_secondary_dark</item>
+        <item name="default_icon_color_disabled">@color/default_icon_color_disabled_dark</item>
+        <item name="default_icon_color_disabled_inverse">@color/default_icon_color_disabled_light</item>
+    </style>
+    <style name="Theme.BrowserUI.DialogWhenLarge" parent="Base.Theme.BrowserUI.DialogWhenLarge"/>
+
     <!-- Unlike |Theme.Chromium.AlertDialog|, this is a complete theme that can be used as an
          activity theme on its own. This should be kept in sync with |Theme.Chromium.AlertDialog|.
          -->
diff --git a/components/browser_ui/widget/android/BUILD.gn b/components/browser_ui/widget/android/BUILD.gn
index be973f56..15e553f 100644
--- a/components/browser_ui/widget/android/BUILD.gn
+++ b/components/browser_ui/widget/android/BUILD.gn
@@ -159,6 +159,7 @@
     "java/res/drawable-mdpi/ic_drag_handle_grey600_24dp.png",
     "java/res/drawable-mdpi/ic_more_vert_24dp_on_dark_bg.png",
     "java/res/drawable-mdpi/ic_more_vert_24dp_on_light_bg.png",
+    "java/res/drawable-v24/rounded_rectangle_surface_1.xml",
     "java/res/drawable-v24/tile_view_icon_background_modern.xml",
     "java/res/drawable-xhdpi/btn_delete_24dp.png",
     "java/res/drawable-xhdpi/btn_info.png",
@@ -196,6 +197,7 @@
     "java/res/drawable/list_item_icon_modern_bg_rect.xml",
     "java/res/drawable/modern_toolbar_text_box_background.xml",
     "java/res/drawable/query_tile_overlay.xml",
+    "java/res/drawable/rounded_rectangle_surface_1.xml",
     "java/res/drawable/search_toolbar_modern_bg.xml",
     "java/res/drawable/tile_view_highlight.xml",
     "java/res/drawable/tile_view_highlight_mask.xml",
diff --git a/components/browser_ui/widget/android/java/res/drawable-v24/rounded_rectangle_surface_1.xml b/components/browser_ui/widget/android/java/res/drawable-v24/rounded_rectangle_surface_1.xml
new file mode 100644
index 0000000..dcec66b
--- /dev/null
+++ b/components/browser_ui/widget/android/java/res/drawable-v24/rounded_rectangle_surface_1.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <corners android:radius="@dimen/default_rounded_corner_radius"/>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/components/browser_ui/widget/android/java/res/drawable/rounded_rectangle_surface_1.xml b/components/browser_ui/widget/android/java/res/drawable/rounded_rectangle_surface_1.xml
new file mode 100644
index 0000000..afe60c5
--- /dev/null
+++ b/components/browser_ui/widget/android/java/res/drawable/rounded_rectangle_surface_1.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2021 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/default_bg_color_elev_1_baseline"/>
+    <corners android:radius="@dimen/default_rounded_corner_radius"/>
+</shape>
diff --git a/components/exo/surface.cc b/components/exo/surface.cc
index fb32fd0..a02c7e1 100644
--- a/components/exo/surface.cc
+++ b/components/exo/surface.cc
@@ -47,6 +47,7 @@
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
 #include "ui/gfx/buffer_format_util.h"
+#include "ui/gfx/buffer_types.h"
 #include "ui/gfx/geometry/dip_util.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_conversions.h"
@@ -798,6 +799,11 @@
     if (!sub_surface->UpdateDisplay(old_display, new_display))
       return false;
   }
+
+  for (auto& observer : observers_) {
+    observer.OnDisplayChanged(this, old_display, new_display);
+  }
+
   return true;
 }
 
@@ -1193,15 +1199,10 @@
             std::move(state_.per_commit_explicit_release_callback_))) {
       current_resource_has_alpha_ =
           FormatHasAlpha(state_.buffer.buffer()->GetFormat());
-      // Planar buffers are sampled as RGB. Technically, the driver is supposed
-      // to preserve the colorspace, so we could still pass the primaries and
-      // transfer function.  However, we don't actually pass the colorspace
-      // to the driver, and it's unclear what drivers would actually do if we
-      // did. So in effect, the colorspace is undefined.
-      if (NumberOfPlanesForLinearBufferFormat(
-              state_.buffer.buffer()->GetFormat()) > 1) {
+      // Setting colors for YUV buffers has been problematic in the past. See
+      // crrev.com/c/2331769
+      if (state_.buffer.buffer()->GetFormat() != gfx::BufferFormat::YVU_420)
         current_resource_.color_space = state_.basic_state.color_space;
-      }
     } else {
       current_resource_.id = viz::kInvalidResourceId;
       // Use the buffer's size, so the AppendContentsToFrame() will append
diff --git a/components/exo/surface_observer.h b/components/exo/surface_observer.h
index f329dc7a..34181ac3 100644
--- a/components/exo/surface_observer.h
+++ b/components/exo/surface_observer.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_EXO_SURFACE_OBSERVER_H_
 #define COMPONENTS_EXO_SURFACE_OBSERVER_H_
 
+#include <cstdint>
+
 namespace exo {
 class Surface;
 
@@ -34,6 +36,12 @@
   // or -1 for a window assigned to all desks.
   virtual void OnDeskChanged(Surface* surface, int state) {}
 
+  // Called when the display of this surface has changed. Only called after
+  // successfully updating sub-surfaces.
+  virtual void OnDisplayChanged(Surface* surface,
+                                int64_t old_display,
+                                int64_t new_display) {}
+
  protected:
   virtual ~SurfaceObserver() {}
 };
diff --git a/components/exo/wayland/server.cc b/components/exo/wayland/server.cc
index 5f97d15..1461f8a 100644
--- a/components/exo/wayland/server.cc
+++ b/components/exo/wayland/server.cc
@@ -375,8 +375,8 @@
                    &zwp_relative_pointer_manager_v1_interface, 1, display_,
                    bind_relative_pointer_manager);
 #if BUILDFLAG(ENABLE_COLOR_MANAGER)
-  wl_global_create(wl_display_.get(), &zcr_color_manager_v1_interface, 1,
-                   display_, bind_zcr_color_manager);
+  wl_global_create(wl_display_.get(), &zcr_color_manager_v1_interface, 1, this,
+                   bind_zcr_color_manager);
 #endif
   wl_global_create(wl_display_.get(), &zxdg_decoration_manager_v1_interface, 1,
                    display_, bind_zxdg_decoration_manager);
diff --git a/components/exo/wayland/zcr_color_manager.cc b/components/exo/wayland/zcr_color_manager.cc
index 9a96f6e5..82fa9e1 100644
--- a/components/exo/wayland/zcr_color_manager.cc
+++ b/components/exo/wayland/zcr_color_manager.cc
@@ -6,14 +6,22 @@
 
 #include <chrome-color-management-server-protocol.h>
 #include <wayland-server-core.h>
+#include <cstdint>
+#include <memory>
 
 #include "base/containers/fixed_flat_map.h"
 #include "base/notreached.h"
 #include "base/strings/stringprintf.h"
+#include "components/exo/surface.h"
+#include "components/exo/surface_observer.h"
+#include "components/exo/wayland/server.h"
 #include "components/exo/wayland/server_util.h"
+#include "components/exo/wm_helper_chromeos.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
 #include "third_party/skia/include/third_party/skcms/skcms.h"
 #include "ui/gfx/color_space.h"
+#include "ui/gfx/display_color_spaces.h"
 #include "ui/gfx/geometry/triangle_f.h"
 
 namespace exo {
@@ -23,6 +31,8 @@
 
 #define PARAM_TO_FLOAT(x) (x / 10000.f)
 
+constexpr auto kDefaultColorSpace = gfx::ColorSpace::CreateSRGB();
+
 constexpr auto kChromaticityMap =
     base::MakeFixedFlatMap<zcr_color_manager_v1_chromaticity_names,
                            gfx::ColorSpace::PrimaryID>(
@@ -64,7 +74,6 @@
  public:
   explicit ColorManagerColorSpace(gfx::ColorSpace color_space)
       : color_space(color_space) {}
-
   virtual ~ColorManagerColorSpace() = default;
 
   const gfx::ColorSpace color_space;
@@ -98,6 +107,63 @@
   }
 };
 
+// Wrap a surface pointer and handle relevant events.
+// TODO(b/207031122): This class should also watch for display color space
+// changes and update clients.
+class ColorManagerSurface final : public SurfaceObserver {
+ public:
+  explicit ColorManagerSurface(Server* server,
+                               wl_resource* color_manager_surface_resource,
+                               Surface* surface)
+      : server_(server),
+        color_manager_surface_resource_(color_manager_surface_resource),
+        scoped_surface_(std::make_unique<ScopedSurface>(surface, this)) {}
+  ColorManagerSurface(ColorManagerSurface&) = delete;
+  ColorManagerSurface(ColorManagerSurface&&) = delete;
+  ~ColorManagerSurface() override = default;
+
+  // Safely set the color space (doing nothing if the surface was destroyed).
+  void SetColorSpace(gfx::ColorSpace color_space) {
+    Surface* surface = scoped_surface_->get();
+    if (!surface)
+      return;
+
+    surface->SetColorSpace(color_space);
+  }
+
+ private:
+  // SurfaceObserver:
+  void OnDisplayChanged(Surface* surface,
+                        int64_t old_display,
+                        int64_t new_display) override {
+    wl_client* client = wl_resource_get_client(color_manager_surface_resource_);
+    wl_resource* display_resource =
+        server_->GetOutputResource(client, new_display);
+
+    if (!display_resource)
+      return;
+
+    const auto* wm_helper = WMHelperChromeOS::GetInstance();
+    const auto& old_display_info = wm_helper->GetDisplayInfo(old_display);
+    const auto& new_display_info = wm_helper->GetDisplayInfo(new_display);
+
+    if (old_display_info.display_color_spaces() ==
+        new_display_info.display_color_spaces())
+      return;
+
+    zcr_color_management_surface_v1_send_preferred_color_space(
+        color_manager_surface_resource_, display_resource);
+  }
+
+  void OnSurfaceDestroying(Surface* surface) override {
+    scoped_surface_.reset();
+  }
+
+  Server* server_;
+  wl_resource* color_manager_surface_resource_;
+  std::unique_ptr<ScopedSurface> scoped_surface_;
+};
+
 ////////////////////////////////////////////////////////////////////////////////
 // zcr_color_management_color_space_v1_interface:
 
@@ -157,21 +223,31 @@
     uint32_t value) {
   NOTIMPLEMENTED();
 }
+
 void color_management_surface_set_color_space(
     struct wl_client* client,
     struct wl_resource* color_management_surface_resource,
-    struct wl_resource* color_space,
+    struct wl_resource* color_space_resource,
     uint32_t render_intent) {
-  NOTIMPLEMENTED();
+  auto* color_manager_color_space =
+      GetUserDataAs<ColorManagerColorSpace>(color_space_resource);
+  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
+      ->SetColorSpace(color_manager_color_space->color_space);
 }
+
 void color_management_surface_set_default_color_space(
     struct wl_client* client,
     struct wl_resource* color_management_surface_resource) {
-  NOTIMPLEMENTED();
+  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
+      ->SetColorSpace(kDefaultColorSpace);
 }
+
 void color_management_surface_destroy(
     struct wl_client* client,
     struct wl_resource* color_management_surface_resource) {
+  GetUserDataAs<ColorManagerSurface>(color_management_surface_resource)
+      ->SetColorSpace(kDefaultColorSpace);
+
   wl_resource_destroy(color_management_surface_resource);
 }
 
@@ -348,13 +424,16 @@
     struct wl_client* client,
     struct wl_resource* color_manager_resource,
     uint32_t id,
-    struct wl_resource* surface) {
+    struct wl_resource* surface_resource) {
   wl_resource* color_management_surface_resource = wl_resource_create(
       client, &zcr_color_management_surface_v1_interface, 1, id);
 
-  wl_resource_set_implementation(color_management_surface_resource,
-                                 &color_management_surface_v1_implementation,
-                                 /*data=*/nullptr, /*destroy=*/nullptr);
+  SetImplementation(color_management_surface_resource,
+                    &color_management_surface_v1_implementation,
+                    std::make_unique<ColorManagerSurface>(
+                        GetUserDataAs<Server>(color_manager_resource),
+                        color_management_surface_resource,
+                        GetUserDataAs<Surface>(surface_resource)));
 }
 
 void color_manager_destroy(struct wl_client* client,
diff --git a/components/feed/feed_feature_list.cc b/components/feed/feed_feature_list.cc
index f837b47..1e870d3 100644
--- a/components/feed/feed_feature_list.cc
+++ b/components/feed/feed_feature_list.cc
@@ -75,8 +75,6 @@
     "FeedClearImageMemoryCache", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kFeedBackToTop{"FeedBackToTop",
                                    base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kFeedSignInPromoDismiss{"FeedSignInPromoDismiss",
-                                            base::FEATURE_ENABLED_BY_DEFAULT};
 const base::Feature kFeedStamp{"FeedStamp", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const char kDefaultReferrerUrl[] = "https://www.google.com/";
diff --git a/components/feed/feed_feature_list.h b/components/feed/feed_feature_list.h
index 5c8b037..913df4f6 100644
--- a/components/feed/feed_feature_list.h
+++ b/components/feed/feed_feature_list.h
@@ -80,10 +80,6 @@
 // feeds quickly.
 extern const base::Feature kFeedBackToTop;
 
-// Feature that enables the 'X' in the signin promo in the Feed. Without the 'X'
-// the signin promo is not dismissible without opting to sign in.
-extern const base::Feature kFeedSignInPromoDismiss;
-
 // Feature that enables StAMP cards in the feed.
 extern const base::Feature kFeedStamp;
 
diff --git a/components/lens/lens_features.cc b/components/lens/lens_features.cc
index 82b1d47d..485403ba 100644
--- a/components/lens/lens_features.cc
+++ b/components/lens/lens_features.cc
@@ -11,10 +11,10 @@
 namespace features {
 
 const base::Feature kLensStandalone{"LensStandalone",
-                                    base::FEATURE_ENABLED_BY_DEFAULT};
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kLensRegionSearch{"LensRegionSearch",
-                                      base::FEATURE_ENABLED_BY_DEFAULT};
+                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::FeatureParam<bool> kRegionSearchMacCursorFix{
     &kLensRegionSearch, "region-search-mac-cursor-fix", true};
@@ -32,13 +32,13 @@
     &kLensRegionSearch, "use-menu-item-alt-text-4", true};
 
 const base::FeatureParam<bool> kEnableUKMLoggingForRegionSearch{
-    &kLensRegionSearch, "region-search-enable-ukm-logging", true};
+    &kLensRegionSearch, "region-search-enable-ukm-logging", false};
 
 const base::FeatureParam<bool> kEnableUKMLoggingForImageSearch{
-    &kLensStandalone, "enable-ukm-logging", true};
+    &kLensStandalone, "enable-ukm-logging", false};
 
 const base::FeatureParam<bool> kEnableSidePanelForLensRegionSearch{
-    &kLensRegionSearch, "region-search-enable-side-panel", false};
+    &kLensRegionSearch, "region-search-enable-side-panel", true};
 
 const base::FeatureParam<bool> kEnableSidePanelForLensImageSearch{
     &kLensStandalone, "enable-side-panel", false};
diff --git a/components/minidump_uploader/BUILD.gn b/components/minidump_uploader/BUILD.gn
index c6ae7a3..6ce34e8 100644
--- a/components/minidump_uploader/BUILD.gn
+++ b/components/minidump_uploader/BUILD.gn
@@ -28,6 +28,7 @@
 android_library("minidump_uploader_java") {
   deps = [
     "//base:base_java",
+    "//net/android:net_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
   ]
 
diff --git a/components/minidump_uploader/DEPS b/components/minidump_uploader/DEPS
index b1646378..ded5e25 100644
--- a/components/minidump_uploader/DEPS
+++ b/components/minidump_uploader/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+components/minidump_uploader/minidump_uploader_jni_headers",
+  "+net",
   "+third_party/crashpad",
 ]
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java
index 9ea0b09..d371afb 100644
--- a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java
+++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java
@@ -4,6 +4,9 @@
 
 package org.chromium.components.minidump_uploader.util;
 
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
+
 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
@@ -12,10 +15,31 @@
  * Default implementation of HttpURLConnectionFactory.
  */
 public class HttpURLConnectionFactoryImpl implements HttpURLConnectionFactory {
+    private static final NetworkTrafficAnnotationTag TRAFFIC_ANNOTATION =
+            NetworkTrafficAnnotationTag.createComplete("minidump_uploader_android",
+                    "semantics {"
+                            + "  sender: 'Minidump Uploader (Android)'"
+                            + "  description: 'Uploads crash reports to Google servers. This data '"
+                            + "               'is used by the Chrome team to track down and fix '"
+                            + "               'critical bugs.'"
+                            + "  trigger: 'During startup, if a crash occurred recently.'"
+                            + "  data: 'Crash report, including a trace and device info.'"
+                            + "  destination: GOOGLE_OWNED_SERVICE"
+                            + "}"
+                            + "policy {"
+                            + "  cookies_allowed: NO"
+                            + "  setting: 'Settings > Google Services > Help improve Chrome\'s '"
+                            + "           'features and performance.'"
+                            + "  policy_exception_justification:"
+                            + "      'MetricsReportingEnabled is only implemented on desktop and '"
+                            + "      'ChromeOS.'"
+                            + "}");
+
     @Override
     public HttpURLConnection createHttpURLConnection(String url) {
         try {
-            return (HttpURLConnection) new URL(url).openConnection();
+            return (HttpURLConnection) ChromiumNetworkAdapter.openConnection(
+                    new URL(url), TRAFFIC_ANNOTATION);
         } catch (IOException e) {
             return null;
         }
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index 2ff1e419..950fa6aa 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -682,8 +682,12 @@
 #endif
 }
 
-void AutocompleteController::InlineTailPrefixes() {
-  result_.InlineTailPrefixes();
+void AutocompleteController::SetTailSuggestContentPrefixes() {
+  result_.SetTailSuggestContentPrefixes();
+}
+
+void AutocompleteController::SetTailSuggestCommonPrefixes() {
+  result_.SetTailSuggestCommonPrefixes();
 }
 
 void AutocompleteController::UpdateResult(
diff --git a/components/omnibox/browser/autocomplete_controller.h b/components/omnibox/browser/autocomplete_controller.h
index d0bfa8b..4ca205e 100644
--- a/components/omnibox/browser/autocomplete_controller.h
+++ b/components/omnibox/browser/autocomplete_controller.h
@@ -167,8 +167,12 @@
   // Constructs and sets the final destination URL on the given match.
   void SetMatchDestinationURL(AutocompleteMatch* match) const;
 
-  // Prepend missing tail suggestion prefixes in results, if present.
-  void InlineTailPrefixes();
+  // Populates tail_suggest_common_prefix on the matches as well as prepends
+  // ellipses.
+  void SetTailSuggestContentPrefixes();
+
+  // Populates tail_suggest_common_prefix on the matches.
+  void SetTailSuggestCommonPrefixes();
 
   HistoryURLProvider* history_url_provider() const {
     return history_url_provider_;
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 0475ad1..3e19e1b2 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -1140,11 +1140,21 @@
   }
 }
 
-void AutocompleteMatch::InlineTailPrefix(const std::u16string& common_prefix) {
+void AutocompleteMatch::SetTailSuggestCommonPrefix(
+    const std::u16string& common_prefix) {
   // Prevent re-addition of prefix.
   if (type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL &&
       tail_suggest_common_prefix.empty()) {
     tail_suggest_common_prefix = common_prefix;
+  }
+}
+
+void AutocompleteMatch::SetTailSuggestContentPrefix(
+    const std::u16string& common_prefix) {
+  // Prevent re-addition of prefix.
+  if (type == AutocompleteMatchType::SEARCH_SUGGEST_TAIL &&
+      tail_suggest_common_prefix.empty()) {
+    SetTailSuggestCommonPrefix(common_prefix);
     // Insert an ellipsis before uncommon part.
     const std::u16string ellipsis = kEllipsis;
     contents = ellipsis + contents;
diff --git a/components/omnibox/browser/autocomplete_match.h b/components/omnibox/browser/autocomplete_match.h
index 4114cde6c..7663b18 100644
--- a/components/omnibox/browser/autocomplete_match.h
+++ b/components/omnibox/browser/autocomplete_match.h
@@ -514,9 +514,11 @@
   void SetAllowedToBeDefault(const AutocompleteInput& input);
 
   // If this match is a tail suggestion, prepends the passed |common_prefix|.
-  // If not, but the prefix matches the beginning of the suggestion, dims that
-  // portion in the classification.
-  void InlineTailPrefix(const std::u16string& common_prefix);
+  void SetTailSuggestCommonPrefix(const std::u16string& common_prefix);
+
+  // If this match is a tail suggestion, prepends the passed |common_prefix|
+  // and adds ellipses to contents.
+  void SetTailSuggestContentPrefix(const std::u16string& common_prefix);
 
   // Estimates dynamic memory usage.
   // See base/trace_event/memory_usage_estimator.h for more info.
diff --git a/components/omnibox/browser/autocomplete_match_unittest.cc b/components/omnibox/browser/autocomplete_match_unittest.cc
index 3a82e1e..45e9ad1 100644
--- a/components/omnibox/browser/autocomplete_match_unittest.cc
+++ b/components/omnibox/browser/autocomplete_match_unittest.cc
@@ -175,7 +175,7 @@
     match.type = AutocompleteMatchType::SEARCH_SUGGEST_TAIL;
     match.contents = base::UTF8ToUTF16(test_case.before_contents);
     match.contents_class = test_case.before_contents_class;
-    match.InlineTailPrefix(u"12345678");
+    match.SetTailSuggestContentPrefix(u"12345678");
     EXPECT_EQ(match.contents, base::UTF8ToUTF16(test_case.after_contents));
     EXPECT_TRUE(EqualClassifications(match.contents_class,
                                      test_case.after_contents_class));
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 1ff3b86..392cae98 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -822,7 +822,7 @@
   });
 }
 
-void AutocompleteResult::InlineTailPrefixes() {
+std::u16string AutocompleteResult::GetCommonPrefix() {
   std::u16string common_prefix;
 
   for (const auto& match : matches_) {
@@ -837,9 +837,25 @@
       break;
     }
   }
-  if (common_prefix.size()) {
+  return common_prefix;
+}
+
+void AutocompleteResult::SetTailSuggestCommonPrefixes() {
+  std::u16string common_prefix = GetCommonPrefix();
+
+  if (!common_prefix.empty()) {
     for (auto& match : matches_)
-      match.InlineTailPrefix(common_prefix);
+      match.SetTailSuggestCommonPrefix(common_prefix);
+  }
+}
+
+void AutocompleteResult::SetTailSuggestContentPrefixes() {
+  std::u16string common_prefix = GetCommonPrefix();
+
+  if (!common_prefix.empty()) {
+    for (auto& match : matches_) {
+      match.SetTailSuggestContentPrefix(common_prefix);
+    }
   }
 }
 
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index 2f15380..489ca28b 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -214,8 +214,15 @@
       const AutocompleteMatch& match,
       AutocompleteProviderClient* provider_client);
 
-  // Prepend missing tail suggestion prefixes in results, if present.
-  void InlineTailPrefixes();
+  // Gets common prefix from SEARCH_SUGGEST_TAIL matches
+  std::u16string GetCommonPrefix();
+
+  // Populates tail_suggest_common_prefix on the matches as well as prepends
+  // ellipses.
+  void SetTailSuggestContentPrefixes();
+
+  // Populates tail_suggest_common_prefix on the matches.
+  void SetTailSuggestCommonPrefixes();
 
   // Estimates dynamic memory usage.
   // See base/trace_event/memory_usage_estimator.h for more info.
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index 12eaf5b..db51a29 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -1941,7 +1941,7 @@
                                   "this is a test");
   AutocompleteResult result;
   result.AppendMatches(AutocompleteInput(), matches);
-  result.InlineTailPrefixes();
+  result.SetTailSuggestContentPrefixes();
   for (size_t i = 0; i < base::size(cases); ++i) {
     EXPECT_EQ(result.match_at(i)->contents,
               base::UTF8ToUTF16(cases[i].after_contents));
@@ -1949,7 +1949,7 @@
                                      cases[i].after_contents_class));
   }
   // Run twice and make sure that it doesn't re-prepend ellipsis.
-  result.InlineTailPrefixes();
+  result.SetTailSuggestContentPrefixes();
   for (size_t i = 0; i < base::size(cases); ++i) {
     EXPECT_EQ(result.match_at(i)->contents,
               base::UTF8ToUTF16(cases[i].after_contents));
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index 0d26cc4..d7138ce 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -83,7 +83,8 @@
     "page_text_dump_result_unittest.cc",
     "page_text_observer_unittest.cc",
   ]
-  if (build_with_tflite_lib) {
+  # crbug.com/1279884 Flaky on CrOS
+  if (!is_chromeos && build_with_tflite_lib) {
     sources += [ "page_content_annotations_model_manager_unittest.cc" ]
   }
   deps = [
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index eaeca49a..d2800611 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -17385,22 +17385,6 @@
       Please note that this policy weakens the protection of local IPs if needed by administrators.''',
     },
     {
-      'name': 'WebRestrictionsAuthority',
-      'owners': ['file://components/policy/resources/OWNERS', 'hendrich@chromium.org'],
-      'id': 341,
-      'type': 'string',
-      'schema' : { 'type': 'string' },
-      'tags': ['filtering', 'admin-sharing'],
-      'future_on': ['webview_android'],
-      'caption': '''Set an external source of URL restrictions''',
-      'desc' : '''When this policy is set to a non-empty string the WebView will read URL restrictions from the content provider with the given authority name.''',
-      'features': {
-        'dynamic_refresh': True,
-        'per_profile': False,
-      },
-      'example_value': 'com.android.chrome.SupervisedUserProvider'
-    },
-    {
       'name': 'ComponentUpdatesEnabled',
       'owners': ['file://components/update_client/OWNERS', 'sorin@chromium.org'],
       'type': 'main',
@@ -30102,7 +30086,7 @@
     },
   ],
   'placeholders': [],
-  'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669, 872],
+  'deleted_policy_ids': [114, 115, 204, 205, 206, 341, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669, 872],
   'deleted_atomic_policy_group_ids': [19],
   'highest_id_currently_used': 934,
   'highest_atomic_group_id_currently_used': 41
diff --git a/components/reporting/util/status.h b/components/reporting/util/status.h
index d432941d..30b182f 100644
--- a/components/reporting/util/status.h
+++ b/components/reporting/util/status.h
@@ -9,7 +9,6 @@
 #include <iosfwd>
 #include <string>
 
-#include "base/compiler_specific.h"
 #include "base/strings/string_piece.h"
 #include "components/reporting/util/status.pb.h"
 
@@ -44,7 +43,7 @@
 };
 }  // namespace error
 
-class WARN_UNUSED_RESULT Status {
+class Status {
  public:
   // Creates a "successful" status.
   Status();
diff --git a/components/reporting/util/statusor.h b/components/reporting/util/statusor.h
index 2c080617..0f065ac 100644
--- a/components/reporting/util/statusor.h
+++ b/components/reporting/util/statusor.h
@@ -81,7 +81,7 @@
 }  // namespace internal
 
 template <typename T>
-class WARN_UNUSED_RESULT StatusOr {
+class StatusOr {
   template <typename U>
   friend class StatusOr;
 
diff --git a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
index 0cca03e..7af7b7f 100644
--- a/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
+++ b/components/safe_browsing/content/browser/safe_browsing_navigation_observer_unittest.cc
@@ -188,7 +188,8 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(SBNavigationObserverTest, TestNavigationEventList) {
+// TODO(crbug.com/1279844): Fix test on Linux TSan bot.
+TEST_F(SBNavigationObserverTest, DISABLED_TestNavigationEventList) {
   NavigationEventList events(3);
 
   EXPECT_EQ(-1, static_cast<int>(events.FindNavigationEvent(
diff --git a/components/search_engines/search_terms_data.cc b/components/search_engines/search_terms_data.cc
index cd86498..52eca74 100644
--- a/components/search_engines/search_terms_data.cc
+++ b/components/search_engines/search_terms_data.cc
@@ -5,7 +5,6 @@
 #include "components/search_engines/search_terms_data.h"
 
 #include "base/check.h"
-#include "build/build_config.h"
 #include "components/google/core/common/google_util.h"
 #include "components/lens/lens_features.h"
 #include "url/gurl.h"
@@ -21,7 +20,6 @@
 std::string SearchTermsData::GoogleBaseSearchByImageURLValue() const {
   const std::string kGoogleHomepageURLPath = std::string("searchbyimage/");
 
-#if !defined(OS_IOS) && !defined(OS_ANDROID)
   // If both LensStandalone and LensRegionSearch features are enabled,
   // LensStandalone parameters will take precedence even if the values differ.
   if (base::FeatureList::IsEnabled(lens::features::kLensStandalone)) {
@@ -29,8 +27,6 @@
   } else if (base::FeatureList::IsEnabled(lens::features::kLensRegionSearch)) {
     return lens::features::GetHomepageURLForRegionSearch();
   }
-#endif  // !defined(OS_IOS) && !defined(OS_ANDROID)
-
   return google_util::kGoogleHomepageURL + kGoogleHomepageURLPath;
 }
 
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index f99bfd8..43ae503 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -15,6 +15,7 @@
 namespace signin_metrics {
 
 // Track all the ways a profile can become signed out as a histogram.
+// Enum SigninSignoutProfile.
 // GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.signin.metrics
 // GENERATED_JAVA_CLASS_NAME_OVERRIDE: SignoutReason
 // These values are persisted to logs. Entries should not be renumbered and
@@ -43,8 +44,7 @@
   AUTHENTICATION_FAILED_WITH_FORCE_SIGNIN = 7,
   // The user disables sync from the DICE UI.
   USER_TUNED_OFF_SYNC_FROM_DICE_UI = 8,
-  // Android specific. Signout forced because the account was removed from the
-  // device.
+  // Signout forced because the account was removed from the device.
   ACCOUNT_REMOVED_FROM_DEVICE = 9,
   // Signin is no longer allowed when the profile is initialized.
   SIGNIN_NOT_ALLOWED_ON_PROFILE_INIT = 10,
@@ -57,6 +57,9 @@
   MOBILE_IDENTITY_CONSISTENCY_ROLLBACK = 13,
   // Sign-out when the account id migration to Gaia ID did not finish,
   ACCOUNT_ID_MIGRATION = 14,
+  // iOS Specific. Sign-out forced because the account was removed from the
+  // device after a device restore.
+  IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE = 15,
   // Keep this as the last enum.
   NUM_PROFILE_SIGNOUT_METRICS,
 };
diff --git a/components/sync/driver/sync_service_crypto.cc b/components/sync/driver/sync_service_crypto.cc
index 310a51fd..b90b4b6 100644
--- a/components/sync/driver/sync_service_crypto.cc
+++ b/components/sync/driver/sync_service_crypto.cc
@@ -122,14 +122,6 @@
             observer_));
   }
 
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override {
-    task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            &SyncEncryptionHandler::Observer::OnBootstrapTokenUpdated,
-            observer_, bootstrap_token));
-  }
-
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override {
     task_runner_->PostTask(
@@ -212,6 +204,28 @@
                                 key.mac_key());
 }
 
+// Serializes |nigori| as bootstrap token. Returns empty string in case of
+// crypto/serialization failures.
+std::string SerializeNigoriAsBootstrapToken(const Nigori& nigori) {
+  sync_pb::NigoriKey proto;
+  nigori.ExportKeys(proto.mutable_deprecated_user_key(),
+                    proto.mutable_encryption_key(), proto.mutable_mac_key());
+
+  const std::string serialized_key = proto.SerializeAsString();
+  if (serialized_key.empty()) {
+    return std::string();
+  }
+
+  std::string encrypted_key;
+  if (!OSCrypt::EncryptString(serialized_key, &encrypted_key)) {
+    return std::string();
+  }
+
+  std::string encoded_key;
+  base::Base64Encode(encrypted_key, &encoded_key);
+  return encoded_key;
+}
+
 }  // namespace
 
 SyncServiceCrypto::State::State()
@@ -345,7 +359,16 @@
       state_.passphrase_key_derivation_params, passphrase);
   DCHECK(nigori);
 
-  return SetDecryptionNigoriKey(std::move(nigori));
+  std::string bootstrap_token = SerializeNigoriAsBootstrapToken(*nigori);
+  if (SetDecryptionNigoriKey(std::move(nigori))) {
+    // Update the bootstrap token immediately, even if engine has new pending
+    // keys, which aren't decryptable with |nigori|, this is harmless as
+    // bootstrap token is ignored if it doesn't contain the right key.
+    delegate_->SetEncryptionBootstrapToken(bootstrap_token);
+    return true;
+  }
+
+  return false;
 }
 
 bool SyncServiceCrypto::IsTrustedVaultKeyRequiredStateKnown() const {
@@ -522,13 +545,6 @@
   delegate_->ReconfigureDataTypesDueToCrypto();
 }
 
-void SyncServiceCrypto::OnBootstrapTokenUpdated(
-    const std::string& bootstrap_token) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(state_.engine);
-  delegate_->EncryptionBootstrapTokenChanged(bootstrap_token);
-}
-
 void SyncServiceCrypto::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                                 bool encrypt_everything) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/sync/driver/sync_service_crypto.h b/components/sync/driver/sync_service_crypto.h
index 6a5ea0b..84fb7d03 100644
--- a/components/sync/driver/sync_service_crypto.h
+++ b/components/sync/driver/sync_service_crypto.h
@@ -35,7 +35,7 @@
     virtual void CryptoStateChanged() = 0;
     virtual void CryptoRequiredUserActionChanged() = 0;
     virtual void ReconfigureDataTypesDueToCrypto() = 0;
-    virtual void EncryptionBootstrapTokenChanged(
+    virtual void SetEncryptionBootstrapToken(
         const std::string& bootstrap_token) = 0;
     virtual std::string GetEncryptionBootstrapToken() = 0;
   };
@@ -84,7 +84,6 @@
   void OnPassphraseAccepted() override;
   void OnTrustedVaultKeyRequired() override;
   void OnTrustedVaultKeyAccepted() override;
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override;
   void OnCryptographerStateChanged(Cryptographer* cryptographer,
diff --git a/components/sync/driver/sync_service_crypto_unittest.cc b/components/sync/driver/sync_service_crypto_unittest.cc
index 4015c63..d148adb5 100644
--- a/components/sync/driver/sync_service_crypto_unittest.cc
+++ b/components/sync/driver/sync_service_crypto_unittest.cc
@@ -82,6 +82,36 @@
   return encoded_key;
 }
 
+MATCHER_P2(BootstrapTokenDerivedFrom,
+           expected_passphrase,
+           expected_derivation_params,
+           "") {
+  const std::string& given_bootstrap_token = arg;
+  std::string decoded_key;
+  if (!base::Base64Decode(given_bootstrap_token, &decoded_key)) {
+    return false;
+  }
+
+  std::string decrypted_key;
+  if (!OSCrypt::DecryptString(decoded_key, &decrypted_key)) {
+    return false;
+  }
+
+  sync_pb::NigoriKey given_key;
+  if (!given_key.ParseFromString(decrypted_key)) {
+    return false;
+  }
+
+  std::unique_ptr<Nigori> expected_nigori = Nigori::CreateByDerivation(
+      expected_derivation_params, expected_passphrase);
+  sync_pb::NigoriKey expected_key;
+  expected_nigori->ExportKeys(expected_key.mutable_deprecated_user_key(),
+                              expected_key.mutable_encryption_key(),
+                              expected_key.mutable_mac_key());
+  return given_key.encryption_key() == expected_key.encryption_key() &&
+         given_key.mac_key() == expected_key.mac_key();
+}
+
 class MockDelegate : public SyncServiceCrypto::Delegate {
  public:
   MockDelegate() = default;
@@ -91,7 +121,7 @@
   MOCK_METHOD(void, CryptoRequiredUserActionChanged, (), (override));
   MOCK_METHOD(void, ReconfigureDataTypesDueToCrypto, (), (override));
   MOCK_METHOD(void,
-              EncryptionBootstrapTokenChanged,
+              SetEncryptionBootstrapToken,
               (const std::string&),
               (override));
   MOCK_METHOD(std::string, GetEncryptionBootstrapToken, (), (override));
@@ -415,6 +445,9 @@
   // after checking the passphrase in the UI thread and a second time later when
   // the engine confirms with OnPassphraseAccepted().
   EXPECT_CALL(delegate_, ReconfigureDataTypesDueToCrypto()).Times(2);
+  EXPECT_CALL(delegate_,
+              SetEncryptionBootstrapToken(BootstrapTokenDerivedFrom(
+                  kTestPassphrase, KeyDerivationParams::CreateForPbkdf2())));
   EXPECT_TRUE(crypto_.SetDecryptionPassphrase(kTestPassphrase));
   EXPECT_FALSE(crypto_.IsPassphraseRequired());
 }
diff --git a/components/sync/driver/sync_service_impl.cc b/components/sync/driver/sync_service_impl.cc
index 87df937..f47e2b7 100644
--- a/components/sync/driver/sync_service_impl.cc
+++ b/components/sync/driver/sync_service_impl.cc
@@ -982,7 +982,7 @@
   NotifyObservers();
 }
 
-void SyncServiceImpl::EncryptionBootstrapTokenChanged(
+void SyncServiceImpl::SetEncryptionBootstrapToken(
     const std::string& bootstrap_token) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   sync_prefs_.SetEncryptionBootstrapToken(bootstrap_token);
diff --git a/components/sync/driver/sync_service_impl.h b/components/sync/driver/sync_service_impl.h
index 02234e5c..b06599a3 100644
--- a/components/sync/driver/sync_service_impl.h
+++ b/components/sync/driver/sync_service_impl.h
@@ -181,8 +181,7 @@
   void CryptoStateChanged() override;
   void CryptoRequiredUserActionChanged() override;
   void ReconfigureDataTypesDueToCrypto() override;
-  void EncryptionBootstrapTokenChanged(
-      const std::string& bootstrap_token) override;
+  void SetEncryptionBootstrapToken(const std::string& bootstrap_token) override;
   std::string GetEncryptionBootstrapToken() override;
 
   // IdentityManager::Observer implementation.
diff --git a/components/sync/driver/sync_user_settings_impl_unittest.cc b/components/sync/driver/sync_user_settings_impl_unittest.cc
index 67713bc..e06e7ac 100644
--- a/components/sync/driver/sync_user_settings_impl_unittest.cc
+++ b/components/sync/driver/sync_user_settings_impl_unittest.cc
@@ -60,7 +60,7 @@
   MOCK_METHOD(void, CryptoRequiredUserActionChanged, (), (override));
   MOCK_METHOD(void, ReconfigureDataTypesDueToCrypto, (), (override));
   MOCK_METHOD(void,
-              EncryptionBootstrapTokenChanged,
+              SetEncryptionBootstrapToken,
               (const std::string&),
               (override));
   MOCK_METHOD(std::string, GetEncryptionBootstrapToken, (), (override));
diff --git a/components/sync/engine/debug_info_event_listener.cc b/components/sync/engine/debug_info_event_listener.cc
index ec242f9..8568d1c 100644
--- a/components/sync/engine/debug_info_event_listener.cc
+++ b/components/sync/engine/debug_info_event_listener.cc
@@ -82,12 +82,6 @@
   CreateAndAddEvent(sync_pb::SyncEnums::TRUSTED_VAULT_KEY_ACCEPTED);
 }
 
-void DebugInfoEventListener::OnBootstrapTokenUpdated(
-    const std::string& bootstrap_token) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CreateAndAddEvent(sync_pb::SyncEnums::BOOTSTRAP_TOKEN_UPDATED);
-}
-
 void DebugInfoEventListener::OnEncryptedTypesChanged(
     ModelTypeSet encrypted_types,
     bool encrypt_everything) {
diff --git a/components/sync/engine/debug_info_event_listener.h b/components/sync/engine/debug_info_event_listener.h
index df5c5b8..02442326 100644
--- a/components/sync/engine/debug_info_event_listener.h
+++ b/components/sync/engine/debug_info_event_listener.h
@@ -64,7 +64,6 @@
   void OnPassphraseAccepted() override;
   void OnTrustedVaultKeyRequired() override;
   void OnTrustedVaultKeyAccepted() override;
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override;
   void OnCryptographerStateChanged(Cryptographer* cryptographer,
diff --git a/components/sync/engine/model_type_registry.cc b/components/sync/engine/model_type_registry.cc
index ef0b6a07..edfbf13 100644
--- a/components/sync/engine/model_type_registry.cc
+++ b/components/sync/engine/model_type_registry.cc
@@ -157,9 +157,6 @@
 
 void ModelTypeRegistry::OnTrustedVaultKeyAccepted() {}
 
-void ModelTypeRegistry::OnBootstrapTokenUpdated(
-    const std::string& bootstrap_token) {}
-
 void ModelTypeRegistry::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                                 bool encrypt_everything) {
   // This does NOT support disabling encryption without reconnecting the
diff --git a/components/sync/engine/model_type_registry.h b/components/sync/engine/model_type_registry.h
index 99e48b9..b691658 100644
--- a/components/sync/engine/model_type_registry.h
+++ b/components/sync/engine/model_type_registry.h
@@ -59,7 +59,6 @@
   void OnPassphraseAccepted() override;
   void OnTrustedVaultKeyRequired() override;
   void OnTrustedVaultKeyAccepted() override;
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override;
   void OnCryptographerStateChanged(Cryptographer* cryptographer,
diff --git a/components/sync/engine/model_type_worker.cc b/components/sync/engine/model_type_worker.cc
index 4ac20212..fed782c 100644
--- a/components/sync/engine/model_type_worker.cc
+++ b/components/sync/engine/model_type_worker.cc
@@ -45,10 +45,10 @@
 
 namespace {
 
-const char kTimeUntilEncryptionKeyFoundHistogramPrefix[] =
-    "Sync.ModelTypeTimeUntilEncryptionKeyFound2.";
-const char kUndecryptablePendingUpdatesDroppedHistogramPrefix[] =
-    "Sync.ModelTypeUndecryptablePendingUpdatesDropped.";
+const char kTimeUntilEncryptionKeyFoundHistogramName[] =
+    "Sync.ModelTypeTimeUntilEncryptionKeyFound2";
+const char kUndecryptablePendingUpdatesDroppedHistogramName[] =
+    "Sync.ModelTypeUndecryptablePendingUpdatesDropped";
 const char kBlockedByUndecryptableUpdateHistogramName[] =
     "Sync.ModelTypeBlockedDueToUndecryptableUpdate";
 
@@ -716,7 +716,10 @@
     // while the cryptographer was pending external interaction.
     if (newly_found_key.get_updates_while_should_have_been_known > 0) {
       base::UmaHistogramCounts1000(
-          base::StrCat({kTimeUntilEncryptionKeyFoundHistogramPrefix,
+          kTimeUntilEncryptionKeyFoundHistogramName,
+          newly_found_key.get_updates_while_should_have_been_known);
+      base::UmaHistogramCounts1000(
+          base::StrCat({kTimeUntilEncryptionKeyFoundHistogramName, ".",
                         ModelTypeToHistogramSuffix(type_)}),
           newly_found_key.get_updates_while_should_have_been_known);
     }
@@ -850,11 +853,15 @@
   });
 
   // If updates were dropped, record how many.
-  if (entries_pending_decryption_.size() < updates_before_dropping) {
+  const size_t dropped_updates =
+      updates_before_dropping - entries_pending_decryption_.size();
+  if (dropped_updates > 0) {
     base::UmaHistogramCounts1000(
-        base::StrCat({kUndecryptablePendingUpdatesDroppedHistogramPrefix,
+        kUndecryptablePendingUpdatesDroppedHistogramName, dropped_updates);
+    base::UmaHistogramCounts1000(
+        base::StrCat({kUndecryptablePendingUpdatesDroppedHistogramName, ".",
                       ModelTypeToHistogramSuffix(type_)}),
-        updates_before_dropping - entries_pending_decryption_.size());
+        dropped_updates);
   }
 }
 
diff --git a/components/sync/engine/model_type_worker_unittest.cc b/components/sync/engine/model_type_worker_unittest.cc
index 888c746..66977e4e 100644
--- a/components/sync/engine/model_type_worker_unittest.cc
+++ b/components/sync/engine/model_type_worker_unittest.cc
@@ -1359,7 +1359,7 @@
   // The fact that the data type is now blocked should have been recorded.
   histogram_tester.ExpectUniqueSample(
       "Sync.ModelTypeBlockedDueToUndecryptableUpdate",
-      ModelTypeHistogramValue(worker()->GetModelType()), 1);
+      ModelTypeForHistograms::kPreferences, 1);
 
   // Send empty GetUpdatesResponse. The counter shouldn't change.
   worker()->ProcessGetUpdatesResponse(
@@ -1386,17 +1386,24 @@
   ApplyUpdates();
 
   // Double check the histogram hasn't been recorded so far.
-  const std::string histogram_name =
-      std::string("Sync.ModelTypeTimeUntilEncryptionKeyFound2.") +
-      ModelTypeToHistogramSuffix(worker()->GetModelType());
-  EXPECT_TRUE(histogram_tester.GetAllSamples(histogram_name).empty());
+  EXPECT_TRUE(histogram_tester
+                  .GetAllSamples("Sync.ModelTypeTimeUntilEncryptionKeyFound2")
+                  .empty());
+  EXPECT_TRUE(histogram_tester
+                  .GetAllSamples(
+                      "Sync.ModelTypeTimeUntilEncryptionKeyFound2.PREFERENCE")
+                  .empty());
 
   // Make the key available. The correct number of GetUpdates cycles should
   // have been recorded.
   DecryptPendingKey();
   ASSERT_EQ(2, get_updates_while_should_have_been_known);
   histogram_tester.ExpectUniqueSample(
-      histogram_name, get_updates_while_should_have_been_known, 1);
+      "Sync.ModelTypeTimeUntilEncryptionKeyFound2",
+      get_updates_while_should_have_been_known, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.ModelTypeTimeUntilEncryptionKeyFound2.PREFERENCE",
+      get_updates_while_should_have_been_known, 1);
 }
 
 TEST_F(ModelTypeWorkerTest, IgnoreUpdatesEncryptedWithKeysMissingForTooLong) {
@@ -1427,9 +1434,9 @@
 
   // Should have recorded that 1 entity was dropped.
   histogram_tester.ExpectUniqueSample(
-      base::StrCat({"Sync.ModelTypeUndecryptablePendingUpdatesDropped.",
-                    ModelTypeToHistogramSuffix(worker()->GetModelType())}),
-      1, 1);
+      "Sync.ModelTypeUndecryptablePendingUpdatesDropped", 1, 1);
+  histogram_tester.ExpectUniqueSample(
+      "Sync.ModelTypeUndecryptablePendingUpdatesDropped.PREFERENCE", 1, 1);
 
   // From now on, incoming updates encrypted with the missing key don't block
   // the worker.
@@ -1439,7 +1446,7 @@
   // Should have recorded that 1 incoming update was ignored.
   histogram_tester.ExpectUniqueSample(
       "Sync.ModelTypeUpdateDrop.DecryptionPendingForTooLong",
-      ModelTypeHistogramValue(worker()->GetModelType()), 1);
+      ModelTypeForHistograms::kPreferences, 1);
 }
 
 // Test that processor has been disconnected from Sync when worker got
diff --git a/components/sync/engine/sync_encryption_handler.h b/components/sync/engine/sync_encryption_handler.h
index 5416301..85de6cc 100644
--- a/components/sync/engine/sync_encryption_handler.h
+++ b/components/sync/engine/sync_encryption_handler.h
@@ -67,17 +67,6 @@
     // accepted and there are no longer pending keys.
     virtual void OnTrustedVaultKeyAccepted() = 0;
 
-    // |bootstrap_token| is an opaque base64 encoded representation of the key
-    // generated by the current passphrase, and is provided to the observer for
-    // persistence purposes and use in a future initialization of sync (e.g.
-    // after restart). The boostrap token will always be derived from the most
-    // recent GAIA password (for accounts with implicit passphrases), even if
-    // the data is still encrypted with an older GAIA password. For accounts
-    // with explicit passphrases, it will be the most recently seen custom
-    // passphrase.
-    virtual void OnBootstrapTokenUpdated(
-        const std::string& bootstrap_token) = 0;
-
     // Called when the set of encrypted types or the encrypt
     // everything flag has been changed. Note that this doesn't imply the
     // encryption is complete.
diff --git a/components/sync/engine/sync_manager_impl.cc b/components/sync/engine/sync_manager_impl.cc
index 130d71e..cd27f1cc 100644
--- a/components/sync/engine/sync_manager_impl.cc
+++ b/components/sync/engine/sync_manager_impl.cc
@@ -222,11 +222,6 @@
   // Does nothing.
 }
 
-void SyncManagerImpl::OnBootstrapTokenUpdated(
-    const std::string& bootstrap_token) {
-  // Does nothing.
-}
-
 void SyncManagerImpl::OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                               bool encrypt_everything) {
   sync_status_tracker_->SetEncryptedTypes(encrypted_types);
diff --git a/components/sync/engine/sync_manager_impl.h b/components/sync/engine/sync_manager_impl.h
index c846539..4fbcf79 100644
--- a/components/sync/engine/sync_manager_impl.h
+++ b/components/sync/engine/sync_manager_impl.h
@@ -95,7 +95,6 @@
   void OnPassphraseAccepted() override;
   void OnTrustedVaultKeyRequired() override;
   void OnTrustedVaultKeyAccepted() override;
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override;
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override;
   void OnCryptographerStateChanged(Cryptographer* cryptographer,
diff --git a/components/sync/engine/sync_manager_impl_unittest.cc b/components/sync/engine/sync_manager_impl_unittest.cc
index 9c5b0393..d7d2513 100644
--- a/components/sync/engine/sync_manager_impl_unittest.cc
+++ b/components/sync/engine/sync_manager_impl_unittest.cc
@@ -108,7 +108,6 @@
   MOCK_METHOD(void, OnPassphraseAccepted, (), (override));
   MOCK_METHOD(void, OnTrustedVaultKeyRequired, (), (override));
   MOCK_METHOD(void, OnTrustedVaultKeyAccepted, (), (override));
-  MOCK_METHOD(void, OnBootstrapTokenUpdated, (const std::string&), (override));
   MOCK_METHOD(void, OnEncryptedTypesChanged, (ModelTypeSet, bool), (override));
   MOCK_METHOD(void,
               OnCryptographerStateChanged,
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index e58e1da9..102901a 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -221,39 +221,12 @@
   return specifics.encrypt_everything() || !old_encrypt_everything;
 }
 
-// Packs explicit passphrase key in order to persist it. Returns empty string in
-// case of errors.
-std::string PackExplicitPassphraseKey(const CryptographerImpl& cryptographer) {
-  DCHECK(cryptographer.CanEncrypt());
-
-  // Explicit passphrase key should always be default one.
-  std::string serialized_key =
-      cryptographer.ExportDefaultKey().SerializeAsString();
-
-  if (serialized_key.empty()) {
-    DLOG(ERROR) << "Failed to serialize explicit passphrase key.";
-    return std::string();
-  }
-
-  std::string encrypted_key;
-  if (!OSCrypt::EncryptString(serialized_key, &encrypted_key)) {
-    DLOG(ERROR) << "Failed to encrypt explicit passphrase key.";
-    return std::string();
-  }
-
-  std::string encoded_key;
-  base::Base64Encode(encrypted_key, &encoded_key);
-  return encoded_key;
-}
-
 }  // namespace
 
 class NigoriSyncBridgeImpl::BroadcastingObserver
     : public SyncEncryptionHandler::Observer {
  public:
-  explicit BroadcastingObserver(
-      const base::RepeatingClosure& post_passphrase_accepted_cb)
-      : post_passphrase_accepted_cb_(post_passphrase_accepted_cb) {}
+  BroadcastingObserver() = default;
 
   BroadcastingObserver(const BroadcastingObserver&) = delete;
   BroadcastingObserver& operator=(const BroadcastingObserver&) = delete;
@@ -281,7 +254,6 @@
     for (auto& observer : observers_) {
       observer.OnPassphraseAccepted();
     }
-    post_passphrase_accepted_cb_.Run();
   }
 
   void OnTrustedVaultKeyRequired() override {
@@ -296,12 +268,6 @@
     }
   }
 
-  void OnBootstrapTokenUpdated(const std::string& bootstrap_token) override {
-    for (auto& observer : observers_) {
-      observer.OnBootstrapTokenUpdated(bootstrap_token);
-    }
-  }
-
   void OnEncryptedTypesChanged(ModelTypeSet encrypted_types,
                                bool encrypt_everything) override {
     for (auto& observer : observers_) {
@@ -328,8 +294,6 @@
   // SyncEncryptionHandlerImpl is no longer needed or consider refactoring old
   // implementation to use checked ObserverList as well.
   base::ObserverList<SyncEncryptionHandler::Observer>::Unchecked observers_;
-
-  const base::RepeatingClosure post_passphrase_accepted_cb_;
 };
 
 NigoriSyncBridgeImpl::NigoriSyncBridgeImpl(
@@ -337,12 +301,7 @@
     std::unique_ptr<NigoriStorage> storage)
     : processor_(std::move(processor)),
       storage_(std::move(storage)),
-      broadcasting_observer_(std::make_unique<BroadcastingObserver>(
-          // base::Unretained() legit because the observer gets destroyed
-          // together with |this|.
-          base::BindRepeating(
-              &NigoriSyncBridgeImpl::MaybeNotifyBootstrapTokenUpdated,
-              base::Unretained(this)))) {
+      broadcasting_observer_(std::make_unique<BroadcastingObserver>()) {
   // TODO(crbug.com/922900): we currently don't verify |deserialized_data|.
   // It's quite unlikely we get a corrupted data, since it was successfully
   // deserialized and decrypted. But we may want to consider some
@@ -542,7 +501,6 @@
   broadcasting_observer_->OnCryptographerStateChanged(
       state_.cryptographer.get(), state_.pending_keys.has_value());
   broadcasting_observer_->OnTrustedVaultKeyAccepted();
-  MaybeNotifyBootstrapTokenUpdated();
 }
 
 base::Time NigoriSyncBridgeImpl::GetKeystoreMigrationTime() {
@@ -935,11 +893,6 @@
   return *state_.custom_passphrase_key_derivation_params;
 }
 
-std::string NigoriSyncBridgeImpl::PackExplicitPassphraseKeyForTesting(
-    const CryptographerImpl& cryptographer) {
-  return PackExplicitPassphraseKey(cryptographer);
-}
-
 base::Time NigoriSyncBridgeImpl::GetExplicitPassphraseTime() const {
   switch (state_.passphrase_type) {
     case NigoriSpecifics::IMPLICIT_PASSPHRASE:
@@ -994,28 +947,6 @@
   }
 }
 
-void NigoriSyncBridgeImpl::MaybeNotifyBootstrapTokenUpdated() const {
-  switch (state_.passphrase_type) {
-    case NigoriSpecifics::UNKNOWN:
-      NOTREACHED();
-      return;
-    case NigoriSpecifics::KEYSTORE_PASSPHRASE:
-    case NigoriSpecifics::TRUSTED_VAULT_PASSPHRASE:
-      return;
-    case NigoriSpecifics::IMPLICIT_PASSPHRASE:
-    case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
-    case NigoriSpecifics::CUSTOM_PASSPHRASE:
-      // |packed_custom_passphrase_key| will be empty in case serialization or
-      // encryption error occurs.
-      std::string packed_custom_passphrase_key =
-          PackExplicitPassphraseKey(*state_.cryptographer);
-      if (!packed_custom_passphrase_key.empty()) {
-        broadcasting_observer_->OnBootstrapTokenUpdated(
-            packed_custom_passphrase_key);
-      }
-  }
-}
-
 sync_pb::NigoriLocalData NigoriSyncBridgeImpl::SerializeAsNigoriLocalData()
     const {
   sync_pb::NigoriLocalData output;
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index b961af6..162ef1e 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -87,9 +87,6 @@
   bool HasPendingKeysForTesting() const;
   KeyDerivationParams GetCustomPassphraseKeyDerivationParamsForTesting() const;
 
-  static std::string PackExplicitPassphraseKeyForTesting(
-      const CryptographerImpl& cryptographer);
-
  private:
   absl::optional<ModelError> UpdateLocalState(
       const sync_pb::NigoriSpecifics& specifics);
@@ -132,11 +129,6 @@
   // the appropriate observer methods (if any).
   void MaybeNotifyOfPendingKeys() const;
 
-  // Persists Nigori derived from explicit passphrase into preferences, in case
-  // error occurs during serialization/encryption, corresponding preference
-  // just won't be updated.
-  void MaybeNotifyBootstrapTokenUpdated() const;
-
   // Queues keystore rotation or full keystore migration if current state
   // assumes it should happen.
   void MaybeTriggerKeystoreReencryption();
diff --git a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
index a4e2f1b..6b322bc 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
@@ -194,7 +194,6 @@
   MOCK_METHOD(void, OnPassphraseAccepted, (), (override));
   MOCK_METHOD(void, OnTrustedVaultKeyRequired, (), (override));
   MOCK_METHOD(void, OnTrustedVaultKeyAccepted, (), (override));
-  MOCK_METHOD(void, OnBootstrapTokenUpdated, (const std::string&), (override));
   MOCK_METHOD(void, OnEncryptedTypesChanged, (ModelTypeSet, bool), (override));
   MOCK_METHOD(void,
               OnCryptographerStateChanged,
@@ -870,7 +869,6 @@
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string())));
   bridge()->SetExplicitPassphraseDecryptionKey(
       MakeNigoriKey(passphrase_key_params));
 
@@ -921,7 +919,6 @@
                                /*encrypted_types=*/EncryptableUserTypes(),
                                /*encrypt_everything=*/true));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string())));
   EXPECT_THAT(bridge()->ApplySyncChanges(absl::nullopt), Eq(absl::nullopt));
   EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori());
 
@@ -986,7 +983,6 @@
                                /*encrypted_types=*/EncryptableUserTypes(),
                                /*encrypt_everything=*/true));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string())));
   EXPECT_THAT(bridge()->ApplySyncChanges(absl::nullopt), Eq(absl::nullopt));
   EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori());
 
@@ -1154,7 +1150,6 @@
   EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated).Times(0);
   bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
 }
@@ -1184,7 +1179,6 @@
       BuildTrustedVaultNigoriSpecifics({kTrustedVaultKey});
 
   EXPECT_CALL(*observer(), OnEncryptedTypesChanged).Times(0);
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated).Times(0);
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/true));
   EXPECT_CALL(*observer(),
@@ -1201,7 +1195,6 @@
   EXPECT_CALL(*observer(), OnTrustedVaultKeyAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated).Times(0);
   bridge()->AddTrustedVaultDecryptionKeys({kTrustedVaultKey});
   EXPECT_FALSE(bridge()->HasPendingKeysForTesting());
   EXPECT_TRUE(bridge()->GetTrustedVaultDebugInfo().has_migration_time());
@@ -1235,7 +1228,6 @@
       BuildTrustedVaultNigoriSpecifics(
           {kTrustedVaultKey, kRotatedTrustedVaultKey});
   EXPECT_CALL(*observer(), OnEncryptedTypesChanged).Times(0);
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated).Times(0);
   EXPECT_CALL(*observer(), OnPassphraseTypeChanged).Times(0);
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/true));
@@ -1290,7 +1282,6 @@
                                /*encrypted_types=*/EncryptableUserTypes(),
                                /*encrypt_everything=*/true));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string())));
   EXPECT_THAT(bridge()->ApplySyncChanges(absl::nullopt), Eq(absl::nullopt));
   EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori());
 }
@@ -1325,7 +1316,6 @@
       /*keystore_key_params=*/kKeystoreKeyParams);
 
   EXPECT_CALL(*observer(), OnEncryptedTypesChanged).Times(0);
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated).Times(0);
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(
@@ -1388,7 +1378,6 @@
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(
                                NotNull(), /*has_pending_keys=*/false));
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
-  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string())));
   bridge()->SetExplicitPassphraseDecryptionKey(
       MakeNigoriKey(kCustomPassphraseKeyParams));
 
diff --git a/components/sync/protocol/proto_enum_conversions.cc b/components/sync/protocol/proto_enum_conversions.cc
index acc1a19..71f77ee 100644
--- a/components/sync/protocol/proto_enum_conversions.cc
+++ b/components/sync/protocol/proto_enum_conversions.cc
@@ -303,7 +303,7 @@
     ENUM_CASE(sync_pb::SyncEnums, PASSPHRASE_TYPE_CHANGED);
     ENUM_CASE(sync_pb::SyncEnums, DEPRECATED_KEYSTORE_TOKEN_UPDATED);
     ENUM_CASE(sync_pb::SyncEnums, CONFIGURE_COMPLETE);
-    ENUM_CASE(sync_pb::SyncEnums, BOOTSTRAP_TOKEN_UPDATED);
+    ENUM_CASE(sync_pb::SyncEnums, DEPRECATED_BOOTSTRAP_TOKEN_UPDATED);
     ENUM_CASE(sync_pb::SyncEnums, TRUSTED_VAULT_KEY_REQUIRED);
     ENUM_CASE(sync_pb::SyncEnums, TRUSTED_VAULT_KEY_ACCEPTED);
   }
diff --git a/components/sync/protocol/sync_enums.proto b/components/sync/protocol/sync_enums.proto
index f4f965b..127ab73 100644
--- a/components/sync/protocol/sync_enums.proto
+++ b/components/sync/protocol/sync_enums.proto
@@ -49,7 +49,7 @@
     // configuration and is once again syncing with the server.
     CONFIGURE_COMPLETE = 12;
     // A new cryptographer bootstrap token was generated.
-    BOOTSTRAP_TOKEN_UPDATED = 13;
+    DEPRECATED_BOOTSTRAP_TOKEN_UPDATED = 13;
     // Cryptographer needs trusted vault decryption keys.
     TRUSTED_VAULT_KEY_REQUIRED = 14;
     // Cryptographer no longer needs trusted vault decryption keys.
diff --git a/components/variations/DEPS b/components/variations/DEPS
index aacb6bfa..ebe4ffd 100644
--- a/components/variations/DEPS
+++ b/components/variations/DEPS
@@ -11,6 +11,7 @@
   "+components/version_info",
   "+crypto",
   "-net",
+  "+net/android",
   "+third_party/protobuf",
   "+third_party/smhasher",
   "+third_party/zlib/google",
diff --git a/components/variations/android/BUILD.gn b/components/variations/android/BUILD.gn
index 1a45b03..c740c32 100644
--- a/components/variations/android/BUILD.gn
+++ b/components/variations/android/BUILD.gn
@@ -8,6 +8,7 @@
   deps = [
     "//base:base_java",
     "//components/variations:variations_java",
+    "//net/android:net_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
   ]
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
diff --git a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
index 964283c..d6f0a25 100644
--- a/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
+++ b/components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java
@@ -18,6 +18,8 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.build.BuildConfig;
 import org.chromium.components.variations.VariationsSwitches;
+import org.chromium.net.ChromiumNetworkAdapter;
+import org.chromium.net.NetworkTrafficAnnotationTag;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -37,6 +39,29 @@
 public class VariationsSeedFetcher {
     private static final String TAG = "VariationsSeedFetch";
 
+    private static final NetworkTrafficAnnotationTag TRAFFIC_ANNOTATION =
+            NetworkTrafficAnnotationTag.createComplete("chrome_variations_android",
+                    "semantics {"
+                            + "  sender: 'Chrome Variations Service (Android)'"
+                            + "  description:"
+                            + "      'The variations service is responsible for determining the '"
+                            + "      'state of field trials in Chrome. These field trials '"
+                            + "      'typically configure either A/B experiments, or launched '"
+                            + "      'features – oftentimes, critical security features.'"
+                            + "  trigger: 'This request is made once, on Chrome\'s first run, to '"
+                            + "           'determine the initial state Chrome should be in.'"
+                            + "  data: 'None.'"
+                            + "  destination: GOOGLE_OWNED_SERVICE"
+                            + "}"
+                            + "policy {"
+                            + "  cookies_allowed: NO"
+                            + "  setting: 'Cannot be disabled in Settings. Chrome Variations are '"
+                            + "           'an essential part of Chrome releases.'"
+                            + "  policy_exception_justification:"
+                            + "      'The ChromeVariations policy is only implemented on desktop '"
+                            + "      'and ChromeOS.'"
+                            + "}");
+
     @IntDef({VariationsPlatform.ANDROID, VariationsPlatform.ANDROID_WEBVIEW})
     @Retention(RetentionPolicy.SOURCE)
     public @interface VariationsPlatform {
@@ -100,7 +125,7 @@
             throws MalformedURLException, IOException {
         String urlString = getConnectionString(platform, restrictMode, milestone, channel);
         URL url = new URL(urlString);
-        return (HttpURLConnection) url.openConnection();
+        return (HttpURLConnection) ChromiumNetworkAdapter.openConnection(url, TRAFFIC_ANNOTATION);
     }
 
     @VisibleForTesting
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 5b849f96..a1a2ea0 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -527,6 +527,11 @@
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilitySlotDisplayContents) {
+  RunHtmlTest(FILE_PATH_LITERAL("slot-display-contents.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
                        AccessibilitySvgStyleElement) {
   RunHtmlTest(FILE_PATH_LITERAL("svg-style-element.html"));
 }
diff --git a/content/browser/android/date_time_chooser_android.cc b/content/browser/android/date_time_chooser_android.cc
index dcf1f2c5..4e927211 100644
--- a/content/browser/android/date_time_chooser_android.cc
+++ b/content/browser/android/date_time_chooser_android.cc
@@ -48,6 +48,7 @@
       date_time_chooser_receiver_(this) {}
 
 DateTimeChooserAndroid::~DateTimeChooserAndroid() {
+  DismissAndDestroyJavaObject();
 }
 
 void DateTimeChooserAndroid::OnDateTimeChooserReceiver(
@@ -99,6 +100,18 @@
     std::move(open_date_time_response_callback_).Run(true, value->dialog_value);
 }
 
+void DateTimeChooserAndroid::CloseDateTimeDialog() {
+  DismissAndDestroyJavaObject();
+}
+
+void DateTimeChooserAndroid::DismissAndDestroyJavaObject() {
+  if (j_date_time_chooser_) {
+    JNIEnv* env = AttachCurrentThread();
+    Java_DateTimeChooserAndroid_dismissAndDestroy(env, j_date_time_chooser_);
+    j_date_time_chooser_.Reset();
+  }
+}
+
 void DateTimeChooserAndroid::ReplaceDateTime(JNIEnv* env,
                                              const JavaRef<jobject>&,
                                              jdouble value) {
diff --git a/content/browser/android/date_time_chooser_android.h b/content/browser/android/date_time_chooser_android.h
index 92a2ada..b8bec02 100644
--- a/content/browser/android/date_time_chooser_android.h
+++ b/content/browser/android/date_time_chooser_android.h
@@ -35,6 +35,8 @@
   void OpenDateTimeDialog(blink::mojom::DateTimeDialogValuePtr value,
                           OpenDateTimeDialogCallback callback) override;
 
+  void CloseDateTimeDialog() override;
+
   // Replaces the current value.
   void ReplaceDateTime(JNIEnv* env,
                        const base::android::JavaRef<jobject>&,
@@ -44,6 +46,8 @@
   void CancelDialog(JNIEnv* env, const base::android::JavaRef<jobject>&);
 
  private:
+  void DismissAndDestroyJavaObject();
+
   friend class content::WebContentsUserData<DateTimeChooserAndroid>;
 
   OpenDateTimeDialogCallback open_date_time_response_callback_;
diff --git a/content/browser/fenced_frame/fenced_frame_unittest.cc b/content/browser/fenced_frame/fenced_frame_unittest.cc
new file mode 100644
index 0000000..303a5f0e
--- /dev/null
+++ b/content/browser/fenced_frame/fenced_frame_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_feature_list.h"
+#include "content/public/test/navigation_simulator.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
+
+namespace content {
+
+class FencedFrameTest : public RenderViewHostImplTestHarness {
+ public:
+  FencedFrameTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
+  }
+  ~FencedFrameTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(FencedFrameTest, FencedFrameSanityTest) {
+  NavigateAndCommit(GURL("https://google.com"));
+  RenderFrameHostImpl* fenced_frame_root = main_test_rfh()->AppendFencedFrame();
+  EXPECT_TRUE(fenced_frame_root->IsFencedFrameRoot());
+  EXPECT_FALSE(fenced_frame_root->GetPage().IsPrimary());
+  EXPECT_EQ(fenced_frame_root->GetParentOrOuterDocument(), main_rfh());
+  EXPECT_TRUE(fenced_frame_root->GetRenderWidgetHost()
+                  ->GetView()
+                  ->IsRenderWidgetHostViewChildFrame());
+
+  // Navigate fenced frame.
+  GURL fenced_frame_url = GURL("https://fencedframe.com");
+  std::unique_ptr<NavigationSimulator> navigation_simulator =
+      NavigationSimulator::CreateForFencedFrame(fenced_frame_url,
+                                                fenced_frame_root);
+  navigation_simulator->Commit();
+  fenced_frame_root = static_cast<RenderFrameHostImpl*>(
+      navigation_simulator->GetFinalRenderFrameHost());
+  EXPECT_TRUE(fenced_frame_root->IsFencedFrameRoot());
+  EXPECT_EQ(fenced_frame_root->GetLastCommittedURL(), fenced_frame_url);
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router_unittest.cc b/content/browser/renderer_host/render_widget_host_input_event_router_unittest.cc
index b99f81a4..477e911 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router_unittest.cc
@@ -81,45 +81,6 @@
   raw_ptr<RenderWidgetHostViewBase> root_view_;
 };
 
-// Used as a target for the RenderWidgetHostInputEventRouter. We record what
-// events were forwarded to us in order to verify that the events are being
-// routed correctly.
-class TestRenderWidgetHostViewChildFrame
-    : public RenderWidgetHostViewChildFrame {
- public:
-  explicit TestRenderWidgetHostViewChildFrame(RenderWidgetHost* widget)
-      : RenderWidgetHostViewChildFrame(widget, display::ScreenInfos()) {
-    Init();
-  }
-  ~TestRenderWidgetHostViewChildFrame() override = default;
-
-  void ProcessGestureEvent(const blink::WebGestureEvent& event,
-                           const ui::LatencyInfo&) override {
-    last_gesture_seen_ = event.GetType();
-  }
-
-  void ProcessAckedTouchEvent(
-      const TouchEventWithLatencyInfo& touch,
-      blink::mojom::InputEventResultState ack_result) override {
-    unique_id_for_last_touch_ack_ = touch.event.unique_touch_event_id;
-  }
-
-  blink::WebInputEvent::Type last_gesture_seen() { return last_gesture_seen_; }
-  uint32_t last_id_for_touch_ack() { return unique_id_for_last_touch_ack_; }
-
-  void Reset() { last_gesture_seen_ = blink::WebInputEvent::Type::kUndefined; }
-
-  void SetCompositor(ui::Compositor* compositor) { compositor_ = compositor; }
-  ui::Compositor* GetCompositor() override { return compositor_; }
-
- private:
-  blink::WebInputEvent::Type last_gesture_seen_ =
-      blink::WebInputEvent::Type::kUndefined;
-  uint32_t unique_id_for_last_touch_ack_ = 0;
-
-  raw_ptr<ui::Compositor> compositor_;
-};
-
 class StubHitTestQuery : public viz::HitTestQuery {
  public:
   StubHitTestQuery(RenderWidgetHostViewBase* hittest_result,
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.h b/content/browser/renderer_host/render_widget_host_view_child_frame.h
index e563196..ee75c29 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.h
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.h
@@ -112,7 +112,7 @@
   void SendInitialPropertiesIfNeeded() override;
   void SetIsLoading(bool is_loading) override;
   void RenderProcessGone() override;
-  void ShowWithVisibility(PageVisibilityState page_visibility) final;
+  void ShowWithVisibility(PageVisibilityState page_visibility) override;
   void Destroy() override;
   void UpdateTooltipUnderCursor(const std::u16string& tooltip_text) override;
   void UpdateTooltipFromKeyboard(const std::u16string& tooltip_text,
diff --git a/content/browser/webrtc/resources/peer_connection_update_table.js b/content/browser/webrtc/resources/peer_connection_update_table.js
index efafbe90..292825d 100644
--- a/content/browser/webrtc/resources/peer_connection_update_table.js
+++ b/content/browser/webrtc/resources/peer_connection_update_table.js
@@ -5,6 +5,7 @@
 import {$} from 'chrome://resources/js/util.m.js';
 
 const MAX_NUMBER_OF_STATE_CHANGES_DISPLAYED = 10;
+const MAX_NUMBER_OF_EXPANDED_MEDIASECTIONS = 10;
 /**
  * The data of a peer connection update.
  * @param {number} pid The id of the renderer.
@@ -165,13 +166,21 @@
         ' (type: "' + type + '", ' + sections.length + ' sections)';
       sections.forEach(section => {
         const lines = section.trim().split('\n');
+        // Extract the mid attribute.
+        const mid = lines
+            .filter(line => line.startsWith('a=mid:'))
+            .map(line => line.substr(6))[0];
         const sectionDetails = document.createElement('details');
-        sectionDetails.open = true;
+        // Fold by default for large SDP.
+        sectionDetails.open =
+          sections.length <= MAX_NUMBER_OF_EXPANDED_MEDIASECTIONS;
         sectionDetails.textContent = lines.slice(1).join('\n');
 
         const sectionSummary = document.createElement('summary');
         sectionSummary.textContent =
-          lines[0].trim() + ' (' + (lines.length - 1) + ' more lines)';
+          lines[0].trim() +
+          ' (' + (lines.length - 1) + ' more lines)' +
+          (mid ? ' mid=' + mid : '');
         sectionDetails.appendChild(sectionSummary);
 
         valueContainer.appendChild(sectionDetails);
diff --git a/content/common/media/cdm_info.cc b/content/common/media/cdm_info.cc
index 59f4649..4f2c0a52 100644
--- a/content/common/media/cdm_info.cc
+++ b/content/common/media/cdm_info.cc
@@ -26,7 +26,7 @@
       version(version),
       path(path),
       file_system_id(file_system_id) {
-  DCHECK(!capability || !capability->encryption_schemes.empty());
+  DCHECK(!this->capability || !this->capability->encryption_schemes.empty());
 }
 
 CdmInfo::CdmInfo(const std::string& key_system,
@@ -37,7 +37,7 @@
       robustness(robustness),
       capability(std::move(capability)),
       type(type) {
-  DCHECK(!capability || !capability->encryption_schemes.empty());
+  DCHECK(!this->capability || !this->capability->encryption_schemes.empty());
 }
 
 CdmInfo::CdmInfo(const CdmInfo& other) = default;
diff --git a/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java b/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
index a86be0e..d4422a0 100644
--- a/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/UiThreadTaskTraitsImpl.java
@@ -60,7 +60,10 @@
     // queues. While in the former case the priority of individual bootstrap tasks is ignored, in
     // the latter case it is used. It is thus important that these tasks have USER_BLOCKING priority
     // so that they are ordered correctly with C++ tasks of type kBootstrap in this latter case.
-    public static final TaskTraits BOOTSTRAP = TaskTraits.USER_BLOCKING.withExtension(
+    // UPDATE: We have reverted Java bootstrap task traits back to having USER_VISIBLE priority
+    // to determine whether changing them to have USER_BLOCKING priority caused a performance
+    // regression.
+    public static final TaskTraits BOOTSTRAP = TaskTraits.USER_VISIBLE.withExtension(
             DESCRIPTOR, new UiThreadTaskTraitsImpl().setTaskType(BrowserTaskType.BOOTSTRAP));
     public static final TaskTraits BEST_EFFORT = DEFAULT.taskPriority(TaskPriority.BEST_EFFORT);
     public static final TaskTraits USER_VISIBLE = DEFAULT.taskPriority(TaskPriority.USER_VISIBLE);
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java b/content/public/android/java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
index ac7841de..79f05cc 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java
@@ -19,24 +19,27 @@
  */
 @JNINamespace("content")
 class DateTimeChooserAndroid {
-
-    private final long mNativeDateTimeChooserAndroid;
+    private long mNativeDateTimeChooserAndroid;
     private final InputDialogContainer mInputDialogContainer;
 
-    private DateTimeChooserAndroid(Context context,
-            long nativeDateTimeChooserAndroid) {
+    private DateTimeChooserAndroid(Context context, long nativeDateTimeChooserAndroid) {
         mNativeDateTimeChooserAndroid = nativeDateTimeChooserAndroid;
-        mInputDialogContainer = new InputDialogContainer(context,
-                new InputDialogContainer.InputActionDelegate() {
-
+        mInputDialogContainer =
+                new InputDialogContainer(context, new InputDialogContainer.InputActionDelegate() {
                     @Override
                     public void replaceDateTime(double value) {
+                        if (mNativeDateTimeChooserAndroid == 0) {
+                            return;
+                        }
                         DateTimeChooserAndroidJni.get().replaceDateTime(
                                 mNativeDateTimeChooserAndroid, DateTimeChooserAndroid.this, value);
                     }
 
                     @Override
                     public void cancelDateTimeDialog() {
+                        if (mNativeDateTimeChooserAndroid == 0) {
+                            return;
+                        }
                         DateTimeChooserAndroidJni.get().cancelDialog(
                                 mNativeDateTimeChooserAndroid, DateTimeChooserAndroid.this);
                     }
@@ -50,6 +53,12 @@
     }
 
     @CalledByNative
+    private void dismissAndDestroy() {
+        mNativeDateTimeChooserAndroid = 0;
+        mInputDialogContainer.dismissDialog();
+    }
+
+    @CalledByNative
     private static DateTimeChooserAndroid createDateTimeChooser(
             WindowAndroid windowAndroid,
             long nativeDateTimeChooserAndroid,
diff --git a/content/public/android/java/src/org/chromium/content/browser/picker/InputDialogContainer.java b/content/public/android/java/src/org/chromium/content/browser/picker/InputDialogContainer.java
index 480f8af5..d6af665 100644
--- a/content/public/android/java/src/org/chromium/content/browser/picker/InputDialogContainer.java
+++ b/content/public/android/java/src/org/chromium/content/browser/picker/InputDialogContainer.java
@@ -274,7 +274,7 @@
         return mDialog != null && mDialog.isShowing();
     }
 
-    private void dismissDialog() {
+    public void dismissDialog() {
         if (isDialogShowing()) mDialog.dismiss();
     }
 
diff --git a/content/public/test/navigation_simulator.h b/content/public/test/navigation_simulator.h
index 6ed671b..1e10568 100644
--- a/content/public/test/navigation_simulator.h
+++ b/content/public/test/navigation_simulator.h
@@ -141,7 +141,14 @@
   static std::unique_ptr<NavigationSimulator> CreateFromPending(
       NavigationController& controller);
 
-  virtual ~NavigationSimulator() {}
+  // Creates a NavigationSimulator that will be used to simulate a
+  // renderer-initiated navigation of a fenced frame root (|render_frame_host|)
+  // to |original_url|.
+  static std::unique_ptr<NavigationSimulator> CreateForFencedFrame(
+      const GURL& original_url,
+      RenderFrameHost* fenced_frame_root);
+
+  virtual ~NavigationSimulator() = default;
 
   // --------------------------------------------------------------------------
 
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h
index 1d17380f..e8eabe3a 100644
--- a/content/public/test/test_renderer_host.h
+++ b/content/public/test/test_renderer_host.h
@@ -135,6 +135,9 @@
 
   // Simulates the receipt of a manifest URL.
   virtual void SimulateManifestURLUpdate(const GURL& manifest_url) = 0;
+
+  // Creates and appends a fenced frame.
+  virtual RenderFrameHost* AppendFencedFrame() = 0;
 };
 
 // An interface and utility for driving tests of RenderViewHost.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index aa15d5a..be410d78 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1988,6 +1988,7 @@
     "../browser/dom_storage/dom_storage_context_wrapper_unittest.cc",
     "../browser/download/download_manager_impl_unittest.cc",
     "../browser/download/save_package_unittest.cc",
+    "../browser/fenced_frame/fenced_frame_unittest.cc",
     "../browser/fenced_frame/fenced_frame_url_mapping_unittest.cc",
     "../browser/file_system/browser_file_system_helper_unittest.cc",
     "../browser/file_system/file_system_operation_runner_unittest.cc",
diff --git a/content/test/data/accessibility/html/slot-display-contents-expected-auralinux.txt b/content/test/data/accessibility/html/slot-display-contents-expected-auralinux.txt
new file mode 100644
index 0000000..4d26449
--- /dev/null
+++ b/content/test/data/accessibility/html/slot-display-contents-expected-auralinux.txt
@@ -0,0 +1,3 @@
+[document web] tag:#document
+++[section] tag:div
+++++[push button] name='Test' tag:button
diff --git a/content/test/data/accessibility/html/slot-display-contents-expected-blink.txt b/content/test/data/accessibility/html/slot-display-contents-expected-blink.txt
new file mode 100644
index 0000000..15817c5
--- /dev/null
+++ b/content/test/data/accessibility/html/slot-display-contents-expected-blink.txt
@@ -0,0 +1,8 @@
+rootWebArea htmlTag='#document'
+++genericContainer ignored htmlTag='html'
+++++genericContainer ignored htmlTag='body'
+++++++genericContainer htmlTag='div'
+++++++++genericContainer ignored htmlTag='slot'
+++++++++++button htmlTag='button' name='Test'
+++++++++++++staticText name='Test'
+++++++++++++++inlineTextBox name='Test'
diff --git a/content/test/data/accessibility/html/slot-display-contents.html b/content/test/data/accessibility/html/slot-display-contents.html
new file mode 100644
index 0000000..18f2a62
--- /dev/null
+++ b/content/test/data/accessibility/html/slot-display-contents.html
@@ -0,0 +1,17 @@
+<!--
+@AURALINUX-ALLOW:tag:*
+@BLINK-ALLOW:htmlTag*
+-->
+<!DOCTYPE html>
+<html>
+<body>
+<div>
+  <template shadowroot="open">
+    <div style="display: contents">
+      <slot></slot>
+    </div>
+  </template>
+  <button>Test</button>
+</div>
+</body>
+</html>
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index d76e154b..6257e29d 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -322,6 +322,20 @@
   return simulator;
 }
 
+// static
+std::unique_ptr<NavigationSimulator> NavigationSimulator::CreateForFencedFrame(
+    const GURL& original_url,
+    RenderFrameHost* fenced_frame_root) {
+  DCHECK(fenced_frame_root->IsFencedFrameRoot());
+  std::unique_ptr<NavigationSimulatorImpl> simulator =
+      NavigationSimulatorImpl::CreateRendererInitiated(original_url,
+                                                       fenced_frame_root);
+  simulator->set_supports_loading_mode_header("fenced-frame");
+  simulator->SetTransition(ui::PAGE_TRANSITION_AUTO_SUBFRAME);
+  simulator->set_should_replace_current_entry(true);
+  return simulator;
+}
+
 NavigationSimulatorImpl::NavigationSimulatorImpl(
     const GURL& original_url,
     bool browser_initiated,
@@ -571,6 +585,9 @@
   }
 
   response_headers_->SetHeader("Content-Type", contents_mime_type_);
+  if (!supports_loading_mode_header_.empty())
+    response_headers_->SetHeader("Supports-Loading-Mode",
+                                 supports_loading_mode_header_);
   PrepareCompleteCallbackOnRequest();
   request_->set_ready_to_commit_callback_for_testing(
       base::BindOnce(&NavigationSimulatorImpl::ReadyToCommitComplete,
diff --git a/content/test/navigation_simulator_impl.h b/content/test/navigation_simulator_impl.h
index d856f55..46d6b43c 100644
--- a/content/test/navigation_simulator_impl.h
+++ b/content/test/navigation_simulator_impl.h
@@ -221,6 +221,10 @@
         has_potentially_trustworthy_unique_origin;
   }
 
+  void set_supports_loading_mode_header(std::string value) {
+    supports_loading_mode_header_ = value;
+  }
+
  private:
   NavigationSimulatorImpl(const GURL& original_url,
                           bool browser_initiated,
@@ -414,6 +418,8 @@
 
   bool early_hints_preload_link_header_received_ = false;
 
+  std::string supports_loading_mode_header_;
+
   absl::optional<bool> was_prerendered_page_activation_;
 
   // These are used to sanity check the content/public/ API calls emitted as
diff --git a/content/test/test_render_frame_host.cc b/content/test/test_render_frame_host.cc
index 3c48bfd..e49b1d5 100644
--- a/content/test/test_render_frame_host.cc
+++ b/content/test/test_render_frame_host.cc
@@ -10,6 +10,7 @@
 
 #include "base/guid.h"
 #include "base/run_loop.h"
+#include "content/browser/fenced_frame/fenced_frame.h"
 #include "content/browser/renderer_host/frame_tree.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/navigator.h"
@@ -240,6 +241,13 @@
   GetPage().UpdateManifestUrl(manifest_url);
 }
 
+TestRenderFrameHost* TestRenderFrameHost::AppendFencedFrame() {
+  fenced_frames_.push_back(
+      std::make_unique<FencedFrame>(weak_ptr_factory_.GetSafeRef()));
+  return static_cast<TestRenderFrameHost*>(
+      fenced_frames_.back().get()->GetInnerRoot());
+}
+
 void TestRenderFrameHost::SendNavigate(int nav_entry_id,
                                        bool did_create_new_entry,
                                        const GURL& url) {
diff --git a/content/test/test_render_frame_host.h b/content/test/test_render_frame_host.h
index 154c432..d86768d3b 100644
--- a/content/test/test_render_frame_host.h
+++ b/content/test/test_render_frame_host.h
@@ -102,6 +102,7 @@
   const std::vector<std::string>& GetConsoleMessages() override;
   int GetHeavyAdIssueCount(HeavyAdIssueType type) override;
   void SimulateManifestURLUpdate(const GURL& manifest_url) override;
+  TestRenderFrameHost* AppendFencedFrame() override;
 
   void SendNavigate(int nav_entry_id,
                     bool did_create_new_entry,
diff --git a/content/test/test_render_view_host.cc b/content/test/test_render_view_host.cc
index fda9f84..0b12d1ff 100644
--- a/content/test/test_render_view_host.cc
+++ b/content/test/test_render_view_host.cc
@@ -288,6 +288,31 @@
   return compositor_;
 }
 
+TestRenderWidgetHostViewChildFrame::TestRenderWidgetHostViewChildFrame(
+    RenderWidgetHost* rwh)
+    : RenderWidgetHostViewChildFrame(rwh, display::ScreenInfos()) {
+  Init();
+}
+
+void TestRenderWidgetHostViewChildFrame::Reset() {
+  last_gesture_seen_ = blink::WebInputEvent::Type::kUndefined;
+}
+
+void TestRenderWidgetHostViewChildFrame::SetCompositor(
+    ui::Compositor* compositor) {
+  compositor_ = compositor;
+}
+
+ui::Compositor* TestRenderWidgetHostViewChildFrame::GetCompositor() {
+  return compositor_;
+}
+
+void TestRenderWidgetHostViewChildFrame::ProcessGestureEvent(
+    const blink::WebGestureEvent& event,
+    const ui::LatencyInfo&) {
+  last_gesture_seen_ = event.GetType();
+}
+
 TestRenderViewHost::TestRenderViewHost(
     FrameTree* frame_tree,
     SiteInstance* instance,
@@ -305,10 +330,16 @@
                          swapped_out,
                          false /* has_initialized_audio_host */),
       delete_counter_(nullptr) {
-  // TestRenderWidgetHostView installs itself into this->view_ in its
-  // constructor, and deletes itself when TestRenderWidgetHostView::Destroy() is
-  // called.
-  new TestRenderWidgetHostView(GetWidget());
+  if (frame_tree->type() == FrameTree::Type::kFencedFrame) {
+    // TestRenderWidgetHostViewChildFrame deletes itself in
+    // RenderWidgetHostViewChildFrame::Destroy.
+    new TestRenderWidgetHostViewChildFrame(GetWidget());
+  } else {
+    // TestRenderWidgetHostView installs itself into this->view_ in
+    // its constructor, and deletes itself when
+    // TestRenderWidgetHostView::Destroy() is called.
+    new TestRenderWidgetHostView(GetWidget());
+  }
 }
 
 TestRenderViewHost::~TestRenderViewHost() {
diff --git a/content/test/test_render_view_host.h b/content/test/test_render_view_host.h
index 4d42bb8..a35ab16 100644
--- a/content/test/test_render_view_host.h
+++ b/content/test/test_render_view_host.h
@@ -18,6 +18,7 @@
 #include "components/viz/host/host_frame_sink_client.h"
 #include "content/browser/renderer_host/render_view_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
+#include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
 #include "content/public/common/page_visibility_state.h"
 #include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/test_renderer_host.h"
@@ -171,6 +172,38 @@
   raw_ptr<ui::Compositor> compositor_ = nullptr;
 };
 
+// TestRenderWidgetHostViewChildFrame -----------------------------------------
+
+// Test version of RenderWidgetHostViewChildFrame to use in unit tests.
+class TestRenderWidgetHostViewChildFrame
+    : public RenderWidgetHostViewChildFrame {
+ public:
+  explicit TestRenderWidgetHostViewChildFrame(RenderWidgetHost* rwh);
+  ~TestRenderWidgetHostViewChildFrame() override = default;
+
+  blink::WebInputEvent::Type last_gesture_seen() { return last_gesture_seen_; }
+
+  void Reset();
+  void SetCompositor(ui::Compositor* compositor);
+  ui::Compositor* GetCompositor() override;
+
+ private:
+  void SetBounds(const gfx::Rect& rect) override {}
+  void Hide() override {}
+  void SetInsets(const gfx::Insets& insets) override {}
+
+  void SendInitialPropertiesIfNeeded() override {}
+  void ShowWithVisibility(PageVisibilityState) override {}
+  void DidNavigate() override {}
+
+  void ProcessGestureEvent(const blink::WebGestureEvent& event,
+                           const ui::LatencyInfo&) override;
+
+  blink::WebInputEvent::Type last_gesture_seen_ =
+      blink::WebInputEvent::Type::kUndefined;
+  raw_ptr<ui::Compositor> compositor_;
+};
+
 // TestRenderViewHost ----------------------------------------------------------
 
 // TODO(brettw) this should use a TestWebContents which should be generalized
@@ -206,9 +239,8 @@
 // similar to (b) above, essentially it gets very tricky.  By using
 // the split interface we avoid complexity within content and maintain
 // reasonable utility for embedders.
-class TestRenderViewHost
-    : public RenderViewHostImpl,
-      public RenderViewHostTester {
+class TestRenderViewHost : public RenderViewHostImpl,
+                           public RenderViewHostTester {
  public:
   TestRenderViewHost(FrameTree* frame_tree,
                      SiteInstance* instance,
diff --git a/device/bluetooth/bluetooth_classic_device_mac.mm b/device/bluetooth/bluetooth_classic_device_mac.mm
index 3794ce8..210802ee 100644
--- a/device/bluetooth/bluetooth_classic_device_mac.mm
+++ b/device/bluetooth/bluetooth_classic_device_mac.mm
@@ -31,20 +31,8 @@
 
 const char kApiUnavailable[] = "This API is not implemented on this platform.";
 
-// Returns the first (should be, only) UUID contained within the
-// |service_class_data|. Returns an invalid (empty) UUID if none is found.
-BluetoothUUID ExtractUuid(IOBluetoothSDPDataElement* service_class_data) {
-  NSArray* inner_elements = [service_class_data getArrayValue];
-  IOBluetoothSDPUUID* sdp_uuid = nil;
-  for (IOBluetoothSDPDataElement* inner_element in inner_elements) {
-    if ([inner_element getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) {
-      sdp_uuid = [[inner_element getUUIDValue] getUUIDWithLength:16];
-      break;
-    }
-  }
-
-  if (!sdp_uuid)
-    return BluetoothUUID();
+BluetoothUUID GetUuid(IOBluetoothSDPUUID* sdp_uuid) {
+  DCHECK(sdp_uuid);
 
   const uint8_t* uuid_bytes =
       reinterpret_cast<const uint8_t*>([sdp_uuid bytes]);
@@ -54,9 +42,23 @@
   uuid_str.insert(13, "-");
   uuid_str.insert(18, "-");
   uuid_str.insert(23, "-");
+
   return BluetoothUUID(uuid_str);
 }
 
+// Returns the first (should be, only) UUID contained within the
+// |service_class_data|. Returns an invalid (empty) UUID if none is found.
+BluetoothUUID ExtractUuid(IOBluetoothSDPDataElement* service_class_data) {
+  NSArray* inner_elements = [service_class_data getArrayValue];
+  for (IOBluetoothSDPDataElement* inner_element in inner_elements) {
+    if ([inner_element getTypeDescriptor] == kBluetoothSDPDataElementTypeUUID) {
+      return GetUuid([[inner_element getUUIDValue] getUUIDWithLength:16]);
+    }
+  }
+
+  return BluetoothUUID();
+}
+
 }  // namespace
 
 BluetoothClassicDeviceMac::BluetoothClassicDeviceMac(
@@ -146,8 +148,15 @@
     IOBluetoothSDPDataElement* service_class_data =
         [service_record getAttributeDataElement:
                             kBluetoothSDPAttributeIdentifierServiceClassIDList];
-    if ([service_class_data getTypeDescriptor] ==
-        kBluetoothSDPDataElementTypeDataElementSequence) {
+    auto type_descriptor = [service_class_data getTypeDescriptor];
+    if (type_descriptor == kBluetoothSDPDataElementTypeUUID) {
+      IOBluetoothSDPUUID* sdp_uuid =
+          [[service_class_data getUUIDValue] getUUIDWithLength:16];
+      BluetoothUUID uuid = GetUuid(sdp_uuid);
+      if (uuid.IsValid())
+        uuids.insert(uuid);
+    } else if (type_descriptor ==
+               kBluetoothSDPDataElementTypeDataElementSequence) {
       BluetoothUUID uuid = ExtractUuid(service_class_data);
       if (uuid.IsValid())
         uuids.insert(uuid);
diff --git a/device/bluetooth/chromeos/bluetooth_utils.cc b/device/bluetooth/chromeos/bluetooth_utils.cc
index a1f5cc31..c9e9007 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.cc
+++ b/device/bluetooth/chromeos/bluetooth_utils.cc
@@ -98,63 +98,10 @@
 
   BluetoothAdapter::DeviceList result;
   for (BluetoothDevice* device : devices) {
-    // Always filter out laptops, etc. There is no intended use case or
-    // Bluetooth profile in this context.
-    if (device->GetDeviceType() == BluetoothDeviceType::COMPUTER) {
+    if (device::IsUnsupportedDevice(device))
       continue;
-    }
 
-    // Always filter out phones. There is no intended use case or Bluetooth
-    // profile in this context.
-    if (base::FeatureList::IsEnabled(
-            chromeos::features::kBluetoothPhoneFilter) &&
-        device->GetDeviceType() == BluetoothDeviceType::PHONE) {
-      continue;
-    }
-
-    // Allow paired devices which are not filtered above to appear in the UI.
-    if (device->IsPaired()) {
-      result.push_back(device);
-      continue;
-    }
-
-    switch (device->GetType()) {
-      // Device with invalid bluetooth transport is filtered out.
-      case BLUETOOTH_TRANSPORT_INVALID:
-        break;
-      // For LE devices, check the service UUID to determine if it supports HID
-      // or second factor authenticator (security key).
-      case BLUETOOTH_TRANSPORT_LE:
-        if (base::Contains(device->GetUUIDs(),
-                           device::BluetoothUUID(kHIDServiceUUID)) ||
-            base::Contains(device->GetUUIDs(),
-                           device::BluetoothUUID(kSecurityKeyServiceUUID))) {
-          result.push_back(device);
-        }
-        break;
-      // For classic mode devices, only filter out if the name is empty because
-      // the device could have an unknown or even known type and still also
-      // provide audio/HID functionality.
-      case BLUETOOTH_TRANSPORT_CLASSIC:
-        if (device->GetName())
-          result.push_back(device);
-        break;
-      // For dual mode devices, a device::BluetoothDevice object without a name
-      // and type/appearance most likely signals that it is truly only a LE
-      // advertisement for a peripheral which is active, but not pairable. Many
-      // popular headphones behave in this exact way. Filter them out until they
-      // provide a type/appearance; this means they've become pairable. See
-      // https://crbug.com/1656971 for more.
-      case BLUETOOTH_TRANSPORT_DUAL:
-        if (device->GetName()) {
-          if (device->GetDeviceType() == BluetoothDeviceType::UNKNOWN) {
-            continue;
-          }
-
-          result.push_back(device);
-        }
-        break;
-    }
+    result.push_back(device);
   }
   return result;
 }
@@ -224,6 +171,72 @@
   return GetLimitedNumDevices(max_devices, filtered_devices);
 }
 
+bool IsUnsupportedDevice(const device::BluetoothDevice* device) {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  if (chromeos::switches::IsUnfilteredBluetoothDevicesEnabled())
+    return false;
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (chromeos::LacrosService::Get()
+          ->init_params()
+          ->is_unfiltered_bluetooth_device_enabled) {
+    return false;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  // Always filter out laptops, etc. There is no intended use case or
+  // Bluetooth profile in this context.
+  if (device->GetDeviceType() == BluetoothDeviceType::COMPUTER)
+    return true;
+
+  // Always filter out phones. There is no intended use case or Bluetooth
+  // profile in this context.
+  if (base::FeatureList::IsEnabled(chromeos::features::kBluetoothPhoneFilter) &&
+      device->GetDeviceType() == BluetoothDeviceType::PHONE) {
+    return true;
+  }
+
+  // Allow paired devices which are not filtered above to appear in the UI.
+  if (device->IsPaired())
+    return false;
+
+  switch (device->GetType()) {
+    // Device with invalid bluetooth transport is filtered out.
+    case BLUETOOTH_TRANSPORT_INVALID:
+      break;
+    // For LE devices, check the service UUID to determine if it supports HID
+    // or second factor authenticator (security key).
+    case BLUETOOTH_TRANSPORT_LE:
+      if (base::Contains(device->GetUUIDs(),
+                         device::BluetoothUUID(kHIDServiceUUID)) ||
+          base::Contains(device->GetUUIDs(),
+                         device::BluetoothUUID(kSecurityKeyServiceUUID))) {
+        return false;
+      }
+      break;
+    // For classic mode devices, only filter out if the name is empty because
+    // the device could have an unknown or even known type and still also
+    // provide audio/HID functionality.
+    case BLUETOOTH_TRANSPORT_CLASSIC:
+      if (device->GetName())
+        return false;
+      break;
+    // For dual mode devices, a device::BluetoothDevice object without a name
+    // and type/appearance most likely signals that it is truly only a LE
+    // advertisement for a peripheral which is active, but not pairable. Many
+    // popular headphones behave in this exact way. Filter them out until they
+    // provide a type/appearance; this means they've become pairable. See
+    // https://crbug.com/1656971 for more.
+    case BLUETOOTH_TRANSPORT_DUAL:
+      if (device->GetName())
+        return device->GetDeviceType() == BluetoothDeviceType::UNKNOWN;
+      break;
+  }
+
+  return true;
+}
+
 void RecordPairingResult(absl::optional<ConnectionFailureReason> failure_reason,
                          BluetoothTransport transport,
                          base::TimeDelta duration) {
diff --git a/device/bluetooth/chromeos/bluetooth_utils.h b/device/bluetooth/chromeos/bluetooth_utils.h
index d86c33e4..4ae192a 100644
--- a/device/bluetooth/chromeos/bluetooth_utils.h
+++ b/device/bluetooth/chromeos/bluetooth_utils.h
@@ -6,6 +6,7 @@
 #define DEVICE_BLUETOOTH_CHROMEOS_BLUETOOTH_UTILS_H_
 
 #include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_device.h"
 #include "device/bluetooth/bluetooth_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -93,6 +94,11 @@
                           BluetoothFilterType filter_type,
                           int max_devices);
 
+// Returns |true| if the device is unsupported and should not be known by the
+// UI.
+DEVICE_BLUETOOTH_EXPORT bool IsUnsupportedDevice(
+    const device::BluetoothDevice* device);
+
 // Record outcome of user attempting to pair to a device.
 DEVICE_BLUETOOTH_EXPORT void RecordPairingResult(
     absl::optional<ConnectionFailureReason> failure_reason,
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc b/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
index 328e79d..b71ea13 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_angle_vulkan.cc
@@ -13,7 +13,10 @@
 #include "gpu/command_buffer/service/shared_image_backing_gl_common.h"
 #include "gpu/command_buffer/service/shared_image_backing_gl_image.h"
 #include "gpu/command_buffer/service/shared_image_representation.h"
+#include "gpu/command_buffer/service/skia_utils.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
+#include "gpu/vulkan/vulkan_fence_helper.h"
+#include "gpu/vulkan/vulkan_image.h"
 #include "gpu/vulkan/vulkan_util.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "ui/gl/gl_context.h"
@@ -31,32 +34,6 @@
   return estimated_size;
 }
 
-GrVkImageInfo CreateGrVkImageInfo(
-    VkImage vk_image,
-    const VkImageCreateInfo& info,
-    const raw_ptr<SharedContextState>& context_state) {
-  DCHECK_NE(vk_image, VK_NULL_HANDLE);
-
-  bool is_protected = info.flags & VK_IMAGE_CREATE_PROTECTED_BIT;
-
-  GrVkImageInfo image_info;
-  image_info.fImage = vk_image;
-  image_info.fAlloc = {};
-  image_info.fImageTiling = info.tiling;
-  image_info.fImageLayout = info.initialLayout;
-  image_info.fFormat = info.format;
-  image_info.fImageUsageFlags = info.usage;
-  image_info.fSampleCount = info.samples;
-  image_info.fLevelCount = info.mipLevels;
-  image_info.fCurrentQueueFamily = context_state->vk_context_provider()
-                                       ->GetDeviceQueue()
-                                       ->GetVulkanQueueIndex();
-  image_info.fProtected = is_protected ? GrProtected::kYes : GrProtected::kNo;
-  image_info.fYcbcrConversionInfo = {};
-
-  return image_info;
-}
-
 using ScopedResetAndRestoreUnpackState =
     SharedImageBackingGLCommon::ScopedResetAndRestoreUnpackState;
 
@@ -85,66 +62,62 @@
         context_state_(context_state) {}
 
   ~AngleVulkanBacking() override {
-    if (!passthrough_texture_)
-      return;
+    if (passthrough_texture_) {
+      if (!gl::GLContext::GetCurrent())
+        context_state_->MakeCurrent(/*surface=*/nullptr, /*needs_gl=*/true);
 
-    if (!gl::GLContext::GetCurrent())
-      context_state_->MakeCurrent(/*surface=*/nullptr, /*needs_gl=*/true);
+      if (!have_context())
+        passthrough_texture_->MarkContextLost();
 
-    if (!have_context())
-      passthrough_texture_->MarkContextLost();
-
-    passthrough_texture_.reset();
+      passthrough_texture_.reset();
+    }
+    auto* fence_helper = context_state_->vk_context_provider()
+                             ->GetDeviceQueue()
+                             ->GetFenceHelper();
+    fence_helper->EnqueueVulkanObjectCleanupForSubmittedWork(
+        std::move(vulkan_image_));
   }
 
   bool Initialize(
       const SharedImageBackingFactoryAngleVulkan::FormatInfo& format_info,
       const SharedImageBackingGLCommon::UnpackStateAttribs& attribs) {
-    SharedImageBackingGLCommon::MakeTextureAndSetParameters(
-        GL_TEXTURE_2D, /*service_id=*/0, /*framebuffer_attachment_angle=*/true,
-        &passthrough_texture_, nullptr);
-    passthrough_texture_->SetEstimatedSize(estimated_size());
+    auto* device_queue =
+        context_state_->vk_context_provider()->GetDeviceQueue();
+    VkFormat vk_format = ToVkFormat(format());
 
-    GLuint texture = passthrough_texture_->service_id();
-
-    gl::GLApi* api = gl::g_current_gl_context;
-    ScopedRestoreTexture scoped_restore(api, GL_TEXTURE_2D);
-    api->glBindTextureFn(GL_TEXTURE_2D, texture);
-
-    if (format_info.supports_storage) {
-      {
-        gl::ScopedProgressReporter scoped_progress_reporter(
-            context_state_->progress_reporter());
-        api->glTexStorage2DEXTFn(GL_TEXTURE_2D, 1,
-                                 format_info.storage_internal_format,
-                                 size().width(), size().height());
+    constexpr auto kUsageNeedsColorAttachment =
+        SHARED_IMAGE_USAGE_GLES2 | SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT |
+        SHARED_IMAGE_USAGE_RASTER | SHARED_IMAGE_USAGE_OOP_RASTERIZATION |
+        SHARED_IMAGE_USAGE_WEBGPU;
+    VkImageUsageFlags vk_usage = VK_IMAGE_USAGE_SAMPLED_BIT |
+                                 VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+                                 VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+    if (usage() & kUsageNeedsColorAttachment) {
+      vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+                  VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+      if (format() == viz::ETC1) {
+        DLOG(ERROR) << "ETC1 format cannot be used as color attachment.";
+        return false;
       }
-
-    } else {
-      ScopedResetAndRestoreUnpackState scoped_unpack_state(api, attribs, false);
-      gl::ScopedProgressReporter scoped_progress_reporter(
-          context_state_->progress_reporter());
-      api->glTexImage2DFn(GL_TEXTURE_2D, 0, format_info.image_internal_format,
-                          size().width(), size().height(), 0,
-                          format_info.adjusted_format, format_info.gl_type,
-                          nullptr);
     }
 
-    if (gl::g_current_gl_driver->ext.b_GL_KHR_debug) {
-      const std::string label =
-          "SharedImage_AngleVulkan" + CreateLabelForSharedImageUsage(usage());
-      api->glObjectLabelFn(GL_TEXTURE, texture, -1, label.c_str());
-    }
+    VkImageCreateFlags vk_flags = 0;
+    auto vulkan_image =
+        VulkanImage::Create(device_queue, size(), vk_format, vk_usage, vk_flags,
+                            VK_IMAGE_TILING_OPTIMAL);
 
-    // Release the texture from ANGLE.
-    api->glReleaseTexturesANGLEFn(1, &texture, &layout_);
+    if (!vulkan_image)
+      return false;
 
-    auto image = base::MakeRefCounted<gl::GLImageEGLAngleVulkan>(size());
-    if (!image->Initialize(texture)) {
-      passthrough_texture_.reset();
+    auto egl_image = base::MakeRefCounted<gl::GLImageEGLAngleVulkan>(size());
+    if (!egl_image->Initialize(vulkan_image->image(),
+                               &vulkan_image->create_info())) {
+      vulkan_image->Destroy();
       return false;
     }
-    image_ = std::move(image);
+
+    vulkan_image_ = std::move(vulkan_image);
+    egl_image_ = std::move(egl_image);
     return true;
   }
 
@@ -167,7 +140,9 @@
   std::unique_ptr<SharedImageRepresentationGLTexturePassthrough>
   ProduceGLTexturePassthrough(SharedImageManager* manager,
                               MemoryTypeTracker* tracker) override {
-    DCHECK(passthrough_texture_);
+    if (!passthrough_texture_ && !InitializePassthroughTexture())
+      return nullptr;
+
     return std::make_unique<SharedImageRepresentationGLTexturePassthroughImpl>(
         manager, this, this, tracker, passthrough_texture_);
   }
@@ -199,14 +174,10 @@
   class SkiaRepresentation;
 
   bool BeginAccessSkia() {
-    VkImageCreateInfo info;
-    VkImage vk_image = image_->ExportVkImage(&info);
-    // Check whether VkImage is re-created in ANGLE.
-    if (vk_image != vk_image_) {
-      vk_image_ = vk_image;
-      backend_texture_ = GrBackendTexture(
-          size().width(), size().height(),
-          CreateGrVkImageInfo(vk_image_, info, context_state_));
+    if (!backend_texture_.isValid()) {
+      GrVkImageInfo info = CreateGrVkImageInfo(vulkan_image_.get());
+      backend_texture_ =
+          GrBackendTexture(size().width(), size().height(), info);
     }
     auto vk_layout = GLImageLayoutToVkImageLayout(layout_);
     backend_texture_.setVkImageLayout(vk_layout);
@@ -220,11 +191,37 @@
     layout_ = VkImageLayoutToGLImageLayout(info.fImageLayout);
   }
 
+  bool InitializePassthroughTexture() {
+    DCHECK(!passthrough_texture_);
+    scoped_refptr<gles2::TexturePassthrough> passthrough_texture;
+    SharedImageBackingGLCommon::MakeTextureAndSetParameters(
+        GL_TEXTURE_2D, /*service_id=*/0,
+        /*framebuffer_attachment_angle=*/true, &passthrough_texture, nullptr);
+    passthrough_texture->SetEstimatedSize(estimated_size());
+
+    GLuint texture = passthrough_texture->service_id();
+
+    gl::GLApi* api = gl::g_current_gl_context;
+    ScopedRestoreTexture scoped_restore(api, GL_TEXTURE_2D);
+    api->glBindTextureFn(GL_TEXTURE_2D, texture);
+
+    if (!egl_image_->BindTexImage(GL_TEXTURE_2D))
+      return false;
+
+    if (gl::g_current_gl_driver->ext.b_GL_KHR_debug) {
+      const std::string label =
+          "SharedImage_AngleVulkan" + CreateLabelForSharedImageUsage(usage());
+      api->glObjectLabelFn(GL_TEXTURE, texture, -1, label.c_str());
+    }
+    passthrough_texture_ = std::move(passthrough_texture);
+    return true;
+  }
+
   raw_ptr<SharedContextState> context_state_;
-  scoped_refptr<gl::GLImageEGLAngleVulkan> image_;
+  std::unique_ptr<VulkanImage> vulkan_image_;
+  scoped_refptr<gl::GLImageEGLAngleVulkan> egl_image_;
   scoped_refptr<gles2::TexturePassthrough> passthrough_texture_;
   GrBackendTexture backend_texture_{};
-  VkImage vk_image_ = VK_NULL_HANDLE;
   GLenum layout_ = GL_NONE;
 };  // namespace
 
diff --git a/gpu/vulkan/vulkan_image.cc b/gpu/vulkan/vulkan_image.cc
index 98fa1c18..1c638c9 100644
--- a/gpu/vulkan/vulkan_image.cc
+++ b/gpu/vulkan/vulkan_image.cc
@@ -114,14 +114,15 @@
   image->device_queue_ = device_queue;
   image->image_ = vk_image;
   image->device_memory_ = vk_device_memory;
-  image->size_ = size;
-  image->format_ = format;
-  image->image_tiling_ = image_tiling;
+  image->create_info_.extent = {static_cast<uint32_t>(size.width()),
+                                static_cast<uint32_t>(size.height()), 1};
+  image->create_info_.format = format;
+  image->create_info_.tiling = image_tiling;
   image->device_size_ = device_size;
   image->memory_type_index_ = memory_type_index;
   image->ycbcr_info_ = ycbcr_info;
-  image->usage_ = usage;
-  image->flags_ = flags;
+  image->create_info_.usage = usage;
+  image->create_info_.flags = flags;
   return image;
 }
 
@@ -184,33 +185,29 @@
   DCHECK(device_memory_ == VK_NULL_HANDLE);
 
   device_queue_ = device_queue;
-  size_ = size;
-  format_ = format;
-  usage_ = usage;
-  flags_ = flags;
-  image_tiling_ = image_tiling;
-
-  VkImageCreateInfo create_info = {
+  create_info_ = {
       .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
       .pNext = vk_image_create_info_next,
-      .flags = flags_,
+      .flags = flags,
       .imageType = VK_IMAGE_TYPE_2D,
-      .format = format_,
+      .format = format,
       .extent = {static_cast<uint32_t>(size.width()),
                  static_cast<uint32_t>(size.height()), 1},
       .mipLevels = 1,
       .arrayLayers = 1,
       .samples = VK_SAMPLE_COUNT_1_BIT,
-      .tiling = image_tiling_,
+      .tiling = image_tiling,
       .usage = usage,
       .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
       .queueFamilyIndexCount = 0,
       .pQueueFamilyIndices = nullptr,
-      .initialLayout = image_layout_,
+      .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
   };
   VkDevice vk_device = device_queue->GetVulkanDevice();
-  VkResult result =
-      vkCreateImage(vk_device, &create_info, nullptr /* pAllocator */, &image_);
+  VkResult result = vkCreateImage(vk_device, &create_info_,
+                                  nullptr /* pAllocator */, &image_);
+  create_info_.pNext = nullptr;
+
   if (result != VK_SUCCESS) {
     DLOG(ERROR) << "vkCreateImage failed result:" << result;
     device_queue_ = nullptr;
@@ -276,7 +273,7 @@
   // initialized in InitializeWithExternalMemoryAndModifiers(). For
   // VK_IMAGE_TILING_OPTIMAL the layout is not usable and
   // vkGetImageSubresourceLayout() is illegal.
-  if (image_tiling_ != VK_IMAGE_TILING_LINEAR)
+  if (image_tiling != VK_IMAGE_TILING_LINEAR)
     return true;
 
   const VkImageSubresource image_subresource = {
diff --git a/gpu/vulkan/vulkan_image.h b/gpu/vulkan/vulkan_image.h
index d705b91c..58269f1 100644
--- a/gpu/vulkan/vulkan_image.h
+++ b/gpu/vulkan/vulkan_image.h
@@ -114,13 +114,16 @@
 #endif
 
   VulkanDeviceQueue* device_queue() const { return device_queue_; }
-  const gfx::Size& size() const { return size_; }
-  VkFormat format() const { return format_; }
-  VkImageCreateFlags flags() const { return flags_; }
-  VkImageUsageFlags usage() const { return usage_; }
+  const VkImageCreateInfo& create_info() const { return create_info_; }
+  gfx::Size size() const {
+    return gfx::Size(create_info_.extent.width, create_info_.extent.height);
+  }
+  VkFormat format() const { return create_info_.format; }
+  VkImageCreateFlags flags() const { return create_info_.flags; }
+  VkImageUsageFlags usage() const { return create_info_.usage; }
   VkDeviceSize device_size() const { return device_size_; }
   uint32_t memory_type_index() const { return memory_type_index_; }
-  VkImageTiling image_tiling() const { return image_tiling_; }
+  VkImageTiling image_tiling() const { return create_info_.tiling; }
   VkImageLayout image_layout() const { return image_layout_; }
   void set_image_layout(VkImageLayout layout) { image_layout_ = layout; }
   uint32_t queue_family_index() const { return queue_family_index_; }
@@ -179,13 +182,9 @@
 #endif
 
   raw_ptr<VulkanDeviceQueue> device_queue_ = nullptr;
-  gfx::Size size_;
-  VkFormat format_ = VK_FORMAT_UNDEFINED;
-  VkImageCreateFlags flags_ = 0;
-  VkImageUsageFlags usage_ = 0;
+  VkImageCreateInfo create_info_{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
   VkDeviceSize device_size_ = 0;
   uint32_t memory_type_index_ = 0;
-  VkImageTiling image_tiling_ = VK_IMAGE_TILING_OPTIMAL;
   VkImageLayout image_layout_ = VK_IMAGE_LAYOUT_UNDEFINED;
   uint32_t queue_family_index_ = VK_QUEUE_FAMILY_IGNORED;
   absl::optional<VulkanYCbCrInfo> ycbcr_info_;
diff --git a/infra/config/generated/builders/try/ios-simulator/properties.textpb b/infra/config/generated/builders/try/ios-simulator/properties.textpb
index 58186a7..b3b23b3 100644
--- a/infra/config/generated/builders/try/ios-simulator/properties.textpb
+++ b/infra/config/generated/builders/try/ios-simulator/properties.textpb
@@ -7,6 +7,9 @@
     ],
     "use_clang_coverage": true
   },
+  "$build/flakiness": {
+    "check_for_flakiness": true
+  },
   "$build/goma": {
     "rpc_extra_params": "?prod",
     "server_host": "goma.chromium.org",
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 4a3de488..c0f51df 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -489,5 +489,5 @@
   * Experiment percentage: 1.0
 
 * [mac11-arm64-rel](https://ci.chromium.org/p/chromium/builders/try/mac11-arm64-rel) ([definition](https://cs.chromium.org/search?q=+file:/try.star$+""mac11-arm64-rel"")) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+""mac11-arm64-rel""))
-  * Experiment percentage: 100.0
+  * Experiment percentage: 50.0
 
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 5c93205..3435ffc0 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -1684,7 +1684,7 @@
       }
       builders {
         name: "chromium/try/mac11-arm64-rel"
-        experiment_percentage: 100
+        experiment_percentage: 50
         location_regexp: ".*"
         location_regexp_exclude: ".+/[+]/docs/.+"
         location_regexp_exclude: ".+/[+]/infra/config/.+"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index d816071..1efbc58c 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -47783,7 +47783,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -47870,7 +47870,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -47957,7 +47957,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48044,7 +48044,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48297,7 +48297,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48384,7 +48384,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48573,7 +48573,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48660,7 +48660,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48747,7 +48747,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48834,7 +48834,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -48921,7 +48921,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49008,7 +49008,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49095,7 +49095,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49182,7 +49182,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49269,7 +49269,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49356,7 +49356,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49443,7 +49443,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49530,7 +49530,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49617,7 +49617,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -49878,7 +49878,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50171,7 +50171,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50456,7 +50456,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50544,7 +50544,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50631,7 +50631,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50718,7 +50718,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50805,7 +50805,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50892,7 +50892,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -50983,7 +50983,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51074,7 +51074,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51161,7 +51161,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51433,7 +51433,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51520,7 +51520,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51608,7 +51608,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51695,7 +51695,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51782,7 +51782,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51869,7 +51869,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -51956,7 +51956,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52043,7 +52043,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52130,7 +52130,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52217,7 +52217,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52304,7 +52304,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52391,7 +52391,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52478,7 +52478,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52565,7 +52565,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52652,7 +52652,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52739,7 +52739,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52826,7 +52826,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -52913,7 +52913,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53001,7 +53001,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53088,7 +53088,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53175,7 +53175,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53263,7 +53263,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53350,7 +53350,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53437,7 +53437,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53524,7 +53524,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53610,7 +53610,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53697,7 +53697,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53784,7 +53784,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53871,7 +53871,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -53958,7 +53958,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54248,7 +54248,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54335,7 +54335,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54421,7 +54421,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54508,7 +54508,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54595,7 +54595,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54682,7 +54682,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -54769,7 +54769,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55038,7 +55038,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55125,7 +55125,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55218,7 +55218,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55305,7 +55305,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55392,7 +55392,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55559,7 +55559,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55643,7 +55643,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55727,7 +55727,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55811,7 +55811,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55896,7 +55896,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -55981,7 +55981,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56066,7 +56066,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56151,7 +56151,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56238,7 +56238,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56408,7 +56408,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56586,7 +56586,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56760,7 +56760,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56847,7 +56847,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -56934,7 +56934,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57021,7 +57021,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57108,7 +57108,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57195,7 +57195,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57281,7 +57281,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57367,7 +57367,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57453,7 +57453,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57539,7 +57539,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57622,7 +57622,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57705,7 +57705,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57788,7 +57788,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57871,7 +57871,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -57954,7 +57954,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58037,7 +58037,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58120,7 +58120,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58203,7 +58203,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58286,7 +58286,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58369,7 +58369,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58452,7 +58452,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58535,7 +58535,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58618,7 +58618,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58701,7 +58701,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58784,7 +58784,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58867,7 +58867,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -58950,7 +58950,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59033,7 +59033,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59116,7 +59116,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59199,7 +59199,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59282,7 +59282,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59365,7 +59365,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59446,7 +59446,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59527,7 +59527,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59608,7 +59608,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59689,7 +59689,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59770,7 +59770,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59851,7 +59851,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -59932,7 +59932,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60013,7 +60013,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60094,7 +60094,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60175,7 +60175,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60256,7 +60256,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60337,7 +60337,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60418,7 +60418,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60501,7 +60501,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60584,7 +60584,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60667,7 +60667,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60750,7 +60750,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60833,7 +60833,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60916,7 +60916,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -60999,7 +60999,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61082,7 +61082,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61165,7 +61165,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61248,7 +61248,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61331,7 +61331,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61414,7 +61414,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61497,7 +61497,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61580,7 +61580,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61663,7 +61663,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61746,7 +61746,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61827,7 +61827,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61908,7 +61908,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -61991,7 +61991,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62162,7 +62162,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62251,7 +62251,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62312,6 +62312,9 @@
         '    ],'
         '    "use_clang_coverage": true'
         '  },'
+        '  "$build/flakiness": {'
+        '    "check_for_flakiness": true'
+        '  },'
         '  "$build/goma": {'
         '    "rpc_extra_params": "?prod",'
         '    "server_host": "goma.chromium.org",'
@@ -62348,7 +62351,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62440,7 +62443,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62540,7 +62543,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62629,7 +62632,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62718,7 +62721,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62807,7 +62810,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62903,7 +62906,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -62992,7 +62995,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63081,7 +63084,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63170,7 +63173,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63259,7 +63262,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63345,7 +63348,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63431,7 +63434,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63518,7 +63521,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63605,7 +63608,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63695,7 +63698,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -63865,7 +63868,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64035,7 +64038,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64122,7 +64125,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64209,7 +64212,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64297,7 +64300,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64384,7 +64387,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64471,7 +64474,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64558,7 +64561,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64645,7 +64648,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64732,7 +64735,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64819,7 +64822,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -64913,7 +64916,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65000,7 +65003,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65087,7 +65090,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65173,7 +65176,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65260,7 +65263,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65347,7 +65350,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65438,7 +65441,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65897,7 +65900,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -65984,7 +65987,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66071,7 +66074,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66158,7 +66161,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66239,7 +66242,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66326,7 +66329,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66413,7 +66416,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66500,7 +66503,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66587,7 +66590,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66675,7 +66678,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66770,7 +66773,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66858,7 +66861,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -66945,7 +66948,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67032,7 +67035,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67205,7 +67208,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67292,7 +67295,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67379,7 +67382,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67466,7 +67469,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67745,7 +67748,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67832,7 +67835,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -67922,7 +67925,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68012,7 +68015,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68096,7 +68099,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68340,7 +68343,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68428,7 +68431,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68515,7 +68518,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68601,7 +68604,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68688,7 +68691,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68775,7 +68778,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68862,7 +68865,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -68949,7 +68952,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69037,7 +69040,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69124,7 +69127,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69211,7 +69214,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69298,7 +69301,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69570,7 +69573,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69657,7 +69660,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69745,7 +69748,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -69833,7 +69836,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70007,7 +70010,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70094,7 +70097,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70185,7 +70188,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70272,7 +70275,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70363,7 +70366,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70451,7 +70454,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70722,7 +70725,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70809,7 +70812,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70896,7 +70899,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -70983,7 +70986,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71070,7 +71073,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71156,7 +71159,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71324,7 +71327,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71489,7 +71492,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71574,7 +71577,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71658,7 +71661,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71743,7 +71746,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -71832,7 +71835,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72109,7 +72112,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72190,7 +72193,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72274,7 +72277,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72358,7 +72361,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72443,7 +72446,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72528,7 +72531,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72613,7 +72616,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72698,7 +72701,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -72965,7 +72968,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73050,7 +73053,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73135,7 +73138,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73220,7 +73223,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73305,7 +73308,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73390,7 +73393,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73475,7 +73478,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73560,7 +73563,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73645,7 +73648,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73731,7 +73734,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73817,7 +73820,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73902,7 +73905,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -73987,7 +73990,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -74071,7 +74074,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -74318,7 +74321,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -74751,7 +74754,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75087,7 +75090,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75175,7 +75178,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75269,7 +75272,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75441,7 +75444,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75614,7 +75617,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -75698,7 +75701,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76102,7 +76105,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76186,7 +76189,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76273,7 +76276,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76360,7 +76363,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76447,7 +76450,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76534,7 +76537,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76627,7 +76630,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76714,7 +76717,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -76998,7 +77001,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77088,7 +77091,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77175,7 +77178,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77262,7 +77265,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77350,7 +77353,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77437,7 +77440,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77525,7 +77528,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77612,7 +77615,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77699,7 +77702,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77786,7 +77789,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77873,7 +77876,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -77960,7 +77963,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
@@ -78127,7 +78130,7 @@
       }
       experiments {
         key: "luci.recipes.use_python3"
-        value: 25
+        value: 100
       }
       experiments {
         key: "luci.use_realms"
diff --git a/infra/config/recipes.star b/infra/config/recipes.star
index fdb15995..9651d9a 100644
--- a/infra/config/recipes.star
+++ b/infra/config/recipes.star
@@ -188,9 +188,7 @@
 build_recipe(
     name = "recipe:chromium_trybot",
     bootstrappable = True,
-    experiments = {
-        "luci.recipes.use_python3": 25,
-    },
+    use_python3 = True,
 )
 
 build_recipe(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 730dbba..815a7ff 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1647,7 +1647,7 @@
     main_list_view = "try",
     orchestrator_cores = 2,
     orchestrator_tryjob = try_.job(
-        experiment_percentage = 100,
+        experiment_percentage = 50,
     ),
     compilator_goma_jobs = goma.jobs.J150,
     compilator_os = os.MAC_11,
@@ -1737,6 +1737,7 @@
 try_.chromium_mac_ios_builder(
     name = "ios-simulator",
     branch_selector = branches.STANDARD_MILESTONE,
+    check_for_flakiness = True,
     main_list_view = "try",
     use_clang_coverage = True,
     coverage_exclude_sources = "ios_test_files_and_test_utils",
diff --git a/ios/chrome/app/BUILD.gn b/ios/chrome/app/BUILD.gn
index 21adee9..d7547c8 100644
--- a/ios/chrome/app/BUILD.gn
+++ b/ios/chrome/app/BUILD.gn
@@ -381,7 +381,6 @@
     "//ios/chrome/browser/variations:ios_chrome_ui_string_overrider_factory",
     "//ios/chrome/browser/voice",
     "//ios/chrome/browser/web",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/browser/web/session_state",
     "//ios/chrome/browser/web_state_list",
diff --git a/ios/chrome/app/application_delegate/BUILD.gn b/ios/chrome/app/application_delegate/BUILD.gn
index 4701c72..2dff684 100644
--- a/ios/chrome/app/application_delegate/BUILD.gn
+++ b/ios/chrome/app/application_delegate/BUILD.gn
@@ -73,7 +73,6 @@
     "//ios/chrome/browser/ui/settings",
     "//ios/chrome/browser/ui/settings:settings_root",
     "//ios/chrome/browser/url_loading",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:test_support",
     "//ios/chrome/common/app_group",
@@ -236,7 +235,6 @@
     "//ios/chrome/browser/ui/ntp:util",
     "//ios/chrome/browser/ui/util",
     "//ios/chrome/browser/url_loading",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:agents",
     "//ios/chrome/browser/web_state_list:session_metrics",
diff --git a/ios/chrome/app/application_delegate/metrics_mediator.mm b/ios/chrome/app/application_delegate/metrics_mediator.mm
index da8b6db..7323dc78 100644
--- a/ios/chrome/app/application_delegate/metrics_mediator.mm
+++ b/ios/chrome/app/application_delegate/metrics_mediator.mm
@@ -19,6 +19,7 @@
 #include "components/metrics/metrics_service.h"
 #include "components/prefs/pref_service.h"
 #import "components/previous_session_info/previous_session_info.h"
+#import "components/signin/public/identity_manager/tribool.h"
 #include "components/ukm/ios/ukm_reporting_ios_util.h"
 #import "ios/chrome/app/application_delegate/metric_kit_subscriber.h"
 #import "ios/chrome/app/application_delegate/startup_information.h"
@@ -29,6 +30,7 @@
 #include "ios/chrome/browser/main/browser.h"
 #include "ios/chrome/browser/metrics/first_user_action_recorder.h"
 #include "ios/chrome/browser/pref_names.h"
+#import "ios/chrome/browser/signin/signin_util.h"
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
 #import "ios/chrome/browser/ui/default_promo/default_browser_utils.h"
@@ -77,6 +79,26 @@
         static_cast<int>(CPENewCredentialUsername::kMaxValue) + 1,
     }};
 
+// Enum values for Startup.IOSColdStartType histogram.
+// Entries should not be renumbered and numeric values should never be reused.
+enum class ColdStartType : int {
+  // Regular cold start.
+  kRegular = 0,
+  // Cold start with FRE.
+  kFirstRun = 1,
+  // Cold start after a device restore.
+  kAfterDeviceRestore = 2,
+  // Cold start after a Chrome upgrade.
+  kAfterChromeUpgrade = 3,
+  // Cold start after a device restore and Chrome upgrade.
+  kAfterDeviceRestoreAndChromeUpgrade = 4,
+  // Unknown device restore.
+  kUnknownDeviceRestore = 5,
+  // Unknown device restore and Chrome upgrade.
+  kUnknownDeviceRestoreAndChromeUpgrade = 6,
+  kMaxValue = kUnknownDeviceRestoreAndChromeUpgrade,
+};
+
 // Returns time delta since app launch as retrieved from kernel info about
 // the current process.
 base::TimeDelta TimeDeltaSinceAppLaunchFromProcess() {
@@ -425,6 +447,34 @@
     [[NSUserDefaults standardUserDefaults]
         removeObjectForKey:kAppEnteredBackgroundDateKey];
   }
+  if (!startupInformation.isColdStart) {
+    return;
+  }
+  signin::Tribool device_restore = IsFirstSessionAfterDeviceRestore();
+  ColdStartType sessionType;
+  if (startupInformation.isFirstRun) {
+    sessionType = ColdStartType::kFirstRun;
+  } else {
+    bool afterUpgrade =
+        [PreviousSessionInfo sharedInstance].isFirstSessionAfterUpgrade;
+    switch (device_restore) {
+      case signin::Tribool::kUnknown:
+        sessionType = afterUpgrade
+                          ? ColdStartType::kUnknownDeviceRestoreAndChromeUpgrade
+                          : ColdStartType::kUnknownDeviceRestore;
+        break;
+      case signin::Tribool::kTrue:
+        sessionType = afterUpgrade
+                          ? ColdStartType::kAfterDeviceRestoreAndChromeUpgrade
+                          : ColdStartType::kAfterDeviceRestore;
+        break;
+      case signin::Tribool::kFalse:
+        sessionType = afterUpgrade ? ColdStartType::kAfterChromeUpgrade
+                                   : ColdStartType::kRegular;
+        break;
+    }
+  }
+  base::UmaHistogramEnumeration("Startup.IOSColdStartType", sessionType);
 }
 
 - (void)updateMetricsStateBasedOnPrefsUserTriggered:(BOOL)isUserTriggered {
diff --git a/ios/chrome/app/application_delegate/user_activity_handler.mm b/ios/chrome/app/application_delegate/user_activity_handler.mm
index 2381ef3..ef15519a 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler.mm
@@ -35,7 +35,6 @@
 #import "ios/chrome/browser/ui/main/connection_information.h"
 #import "ios/chrome/browser/url_loading/image_search_param_generator.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/common/intents/OpenInChromeIncognitoIntent.h"
 #import "ios/chrome/common/intents/OpenInChromeIntent.h"
@@ -652,7 +651,7 @@
     WebStateList* webStateList = browser->GetWebStateList();
     for (int index = 0; index < webStateList->count(); ++index) {
       web::WebState* webState = webStateList->GetWebStateAt(index);
-      NSString* currentTabID = TabIdTabHelper::FromWebState(webState)->tab_id();
+      NSString* currentTabID = webState->GetStableIdentifier();
       if ([currentTabID isEqualToString:tabID]) {
         U2FTabHelper::FromWebState(webState)->EvaluateU2FResult(URL);
         return;
diff --git a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
index 6ad84e3..2cd0817 100644
--- a/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
+++ b/ios/chrome/app/application_delegate/user_activity_handler_unittest.mm
@@ -35,7 +35,6 @@
 #import "ios/chrome/browser/ui/main/test/fake_connection_information.h"
 #import "ios/chrome/browser/ui/main/test/stub_browser_interface_provider.h"
 #import "ios/chrome/browser/url_loading/url_loading_params.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_opener.h"
@@ -126,7 +125,7 @@
   }
 
   NSString* GetTabIdForWebState(web::WebState* web_state) {
-    return TabIdTabHelper::FromWebState(web_state)->tab_id();
+    return web_state->GetStableIdentifier();
   }
 
   conditionBlock getCompletionHandler() {
@@ -835,7 +834,6 @@
       std::make_unique<WebStateList>(&_webStateListDelegate);
 
   auto web_state = std::make_unique<web::FakeWebState>();
-  TabIdTabHelper::CreateForWebState(web_state.get());
   FakeU2FTabHelper::CreateForWebState(web_state.get());
   web::WebState* web_state_ptr = web_state.get();
   web_state_list_->InsertWebState(
diff --git a/ios/chrome/browser/app_launcher/BUILD.gn b/ios/chrome/browser/app_launcher/BUILD.gn
index 04902bff..1d71cd0a 100644
--- a/ios/chrome/browser/app_launcher/BUILD.gn
+++ b/ios/chrome/browser/app_launcher/BUILD.gn
@@ -88,7 +88,6 @@
     "//ios/chrome/browser/prefs:browser_prefs",
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/u2f",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web:web_internal",
     "//ios/chrome/browser/web_state_list",
     "//ios/web/common:features",
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
index d7d2487..a6cd692 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper_unittest.mm
@@ -25,7 +25,6 @@
 #include "ios/chrome/browser/policy_url_blocking/policy_url_blocking_service.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
 #import "ios/chrome/browser/u2f/u2f_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/test/fakes/fake_navigation_manager.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
@@ -150,7 +149,6 @@
     ReadingListModelFactory::GetInstance()->SetTestingFactoryAndUse(
         chrome_browser_state_.get(),
         base::BindRepeating(&BuildReadingListModel));
-    TabIdTabHelper::CreateForWebState(&web_state_);
     is_reading_list_initialized_ = true;
   }
 
@@ -433,8 +431,6 @@
 #define MAYBE_U2FUrls DISABLED_U2FUrls
 #endif
 TEST_F(AppLauncherTabHelperTest, MAYBE_U2FUrls) {
-  // Add required tab helpers for the U2F check.
-  TabIdTabHelper::CreateForWebState(&web_state_);
   std::unique_ptr<web::NavigationItem> item = web::NavigationItem::Create();
 
   // "u2f-x-callback" scheme should only be created by the browser. External
diff --git a/ios/chrome/browser/crash_report/BUILD.gn b/ios/chrome/browser/crash_report/BUILD.gn
index 62ea9d8..3dd0958 100644
--- a/ios/chrome/browser/crash_report/BUILD.gn
+++ b/ios/chrome/browser/crash_report/BUILD.gn
@@ -82,7 +82,6 @@
     "//ios/chrome/browser/sessions:session_service",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/main:scene_state_header",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/web",
     "//net",
diff --git a/ios/chrome/browser/crash_report/crash_report_helper.mm b/ios/chrome/browser/crash_report/crash_report_helper.mm
index 98dd297..5349698 100644
--- a/ios/chrome/browser/crash_report/crash_report_helper.mm
+++ b/ios/chrome/browser/crash_report/crash_report_helper.mm
@@ -23,7 +23,6 @@
 #include "ios/chrome/browser/crash_report/crash_keys_helper.h"
 #import "ios/chrome/browser/crash_report/crash_report_user_application_state.h"
 #include "ios/chrome/browser/crash_report/crash_reporter_url_observer.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -166,29 +165,28 @@
 - (void)webStateList:(WebStateList*)webStateList
     didDetachWebState:(web::WebState*)webState
               atIndex:(int)atIndex {
-  [self removeTabId:TabIdTabHelper::FromWebState(webState)->tab_id()];
+  [self removeTabId:webState->GetStableIdentifier()];
 }
 
 - (void)webStateList:(WebStateList*)webStateList
     didReplaceWebState:(web::WebState*)oldWebState
           withWebState:(web::WebState*)newWebState
                atIndex:(int)atIndex {
-  [self removeTabId:TabIdTabHelper::FromWebState(oldWebState)->tab_id()];
+  [self removeTabId:oldWebState->GetStableIdentifier()];
 }
 
 #pragma mark - CRWWebStateObserver protocol
 
 - (void)webState:(web::WebState*)webState
     didStartNavigation:(web::NavigationContext*)navigation {
-  NSString* tabID = TabIdTabHelper::FromWebState(webState)->tab_id();
-  [self closingDocumentInTab:tabID];
+  [self closingDocumentInTab:webState->GetStableIdentifier()];
 }
 
 - (void)webState:(web::WebState*)webState
     didLoadPageWithSuccess:(BOOL)loadSuccess {
   if (!loadSuccess || webState->GetContentsMimeType() != "application/pdf")
     return;
-  NSString* tabID = TabIdTabHelper::FromWebState(webState)->tab_id();
+  NSString* tabID = webState->GetStableIdentifier();
   NSString* oldMime = (NSString*)[self tabInfo:@"mime" forTab:tabID];
   if ([kDocumentMimeType isEqualToString:oldMime])
     return;
diff --git a/ios/chrome/browser/drag_and_drop/BUILD.gn b/ios/chrome/browser/drag_and_drop/BUILD.gn
index 4a3f22d..0a693f2 100644
--- a/ios/chrome/browser/drag_and_drop/BUILD.gn
+++ b/ios/chrome/browser/drag_and_drop/BUILD.gn
@@ -14,7 +14,6 @@
   ]
   deps = [
     "//base",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/window_activities",
     "//ios/web/public",
     "//net",
diff --git a/ios/chrome/browser/drag_and_drop/drag_item_util.mm b/ios/chrome/browser/drag_and_drop/drag_item_util.mm
index 4404c1f..591a358c 100644
--- a/ios/chrome/browser/drag_and_drop/drag_item_util.mm
+++ b/ios/chrome/browser/drag_and_drop/drag_item_util.mm
@@ -5,7 +5,6 @@
 #import "ios/chrome/browser/drag_and_drop/drag_item_util.h"
 
 #include "base/check_op.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/window_activities/window_activity_helpers.h"
 #include "ios/web/public/browser_state.h"
 #import "ios/web/public/web_state.h"
@@ -43,7 +42,7 @@
   NSItemProvider* item_provider = [[NSItemProvider alloc] initWithObject:url];
   UIDragItem* drag_item =
       [[UIDragItem alloc] initWithItemProvider:item_provider];
-  NSString* tab_id = TabIdTabHelper::FromWebState(web_state)->tab_id();
+  NSString* tab_id = web_state->GetStableIdentifier();
   BOOL incognito = web_state->GetBrowserState()->IsOffTheRecord();
   // Visibility "all" is required to allow the OS to recognize this activity for
   // creating a new window.
diff --git a/ios/chrome/browser/main/BUILD.gn b/ios/chrome/browser/main/BUILD.gn
index 3a17e19..660d0de 100644
--- a/ios/chrome/browser/main/BUILD.gn
+++ b/ios/chrome/browser/main/BUILD.gn
@@ -65,7 +65,6 @@
     "//ios/chrome/browser/url_loading",
     "//ios/chrome/browser/web",
     "//ios/chrome/browser/web:delegate",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:agents",
     "//ios/chrome/browser/web_state_list:session_metrics",
@@ -113,7 +112,6 @@
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/tabs",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/browser/web_state_list:test_support",
     "//ios/web/public/test",
diff --git a/ios/chrome/browser/main/browser_util.mm b/ios/chrome/browser/main/browser_util.mm
index d90ef10a..241924ead 100644
--- a/ios/chrome/browser/main/browser_util.mm
+++ b/ios/chrome/browser/main/browser_util.mm
@@ -15,9 +15,9 @@
 #import "ios/chrome/browser/main/browser_list_factory.h"
 #import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_opener.h"
+#import "ios/web/public/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -34,8 +34,7 @@
     WebStateList* web_state_list = browser->GetWebStateList();
     for (int i = 0; i < web_state_list->count(); ++i) {
       web::WebState* web_state = web_state_list->GetWebStateAt(i);
-      NSString* current_tab_id =
-          TabIdTabHelper::FromWebState(web_state)->tab_id();
+      NSString* current_tab_id = web_state->GetStableIdentifier();
       if ([current_tab_id isEqualToString:tab_id]) {
         tab_index = i;
         return browser;
diff --git a/ios/chrome/browser/main/browser_util_unittest.mm b/ios/chrome/browser/main/browser_util_unittest.mm
index 1bbea73..e05f6b52 100644
--- a/ios/chrome/browser/main/browser_util_unittest.mm
+++ b/ios/chrome/browser/main/browser_util_unittest.mm
@@ -12,7 +12,6 @@
 #include "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_opener.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
@@ -60,9 +59,7 @@
   web::FakeWebState* AppendNewWebState(Browser* browser) {
     auto fake_web_state = std::make_unique<web::FakeWebState>();
     web::FakeWebState* inserted_web_state = fake_web_state.get();
-    TabIdTabHelper::CreateForWebState(inserted_web_state);
-    NSString* tab_id =
-        TabIdTabHelper::FromWebState(inserted_web_state)->tab_id();
+    NSString* tab_id = inserted_web_state->GetStableIdentifier();
     SnapshotTabHelper::CreateForWebState(inserted_web_state, tab_id);
     browser->GetWebStateList()->InsertWebState(
         WebStateList::kInvalidIndex, std::move(fake_web_state),
@@ -73,7 +70,7 @@
   // Returns the tab ID for the web state at |index| in |browser|.
   NSString* GetTabIDForWebStateAt(int index, Browser* browser) {
     web::WebState* web_state = browser->GetWebStateList()->GetWebStateAt(index);
-    return TabIdTabHelper::FromWebState(web_state)->tab_id();
+    return web_state->GetStableIdentifier();
   }
 
   web::WebTaskEnvironment task_environment_;
diff --git a/ios/chrome/browser/open_in/BUILD.gn b/ios/chrome/browser/open_in/BUILD.gn
index 9836c27..dae625b 100644
--- a/ios/chrome/browser/open_in/BUILD.gn
+++ b/ios/chrome/browser/open_in/BUILD.gn
@@ -35,7 +35,6 @@
     "//ios/chrome/browser",
     "//ios/chrome/browser/browser_state:test_support",
     "//ios/chrome/browser/ui/open_in",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/web/public/test/fakes",
     "//testing/gtest",
     "//ui/base:base",
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index c242d13..b3f9392a 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -61,7 +61,6 @@
 #import "ios/chrome/browser/ui/commands/command_dispatcher.h"
 #import "ios/chrome/browser/ui/commands/password_breach_commands.h"
 #import "ios/chrome/browser/ui/commands/password_protection_commands.h"
-#include "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/web/common/url_scheme_util.h"
 #include "ios/web/public/js_messaging/web_frame.h"
@@ -323,9 +322,8 @@
                    iconURL:formSignedIn->icon_url
           URLLoaderFactory:_webState->GetBrowserState()
                                ->GetSharedURLLoaderFactory()];
-  TabIdTabHelper* tabIdHelper = TabIdTabHelper::FromWebState(_webState);
   if (![_delegate displaySignInNotification:self.notifyAutoSigninViewController
-                                  fromTabId:tabIdHelper->tab_id()]) {
+                                  fromTabId:_webState->GetStableIdentifier()]) {
     // The notification was not shown. Store the password form in
     // |_pendingAutoSigninPasswordForm| to show the notification later.
     _pendingAutoSigninPasswordForm = std::move(formSignedIn);
diff --git a/ios/chrome/browser/signin/authentication_service.h b/ios/chrome/browser/signin/authentication_service.h
index d4a98c2..165b2f13 100644
--- a/ios/chrome/browser/signin/authentication_service.h
+++ b/ios/chrome/browser/signin/authentication_service.h
@@ -178,8 +178,11 @@
   //
   // |should_prompt| indicates whether the user should be prompted with the
   // resign-in infobar if the method signs out.
+  // |device_restore| should be true only when called from |Initialize()| and
+  // Chrome is started after a device restore.
   void HandleForgottenIdentity(ChromeIdentity* invalid_identity,
-                               bool should_prompt);
+                               bool should_prompt,
+                               bool device_restore);
 
   // Checks if the authenticated identity was removed by calling
   // |HandleForgottenIdentity|. Reloads the OAuth2 token service accounts if the
diff --git a/ios/chrome/browser/signin/authentication_service.mm b/ios/chrome/browser/signin/authentication_service.mm
index 6da59f6..e20bd47 100644
--- a/ios/chrome/browser/signin/authentication_service.mm
+++ b/ios/chrome/browser/signin/authentication_service.mm
@@ -7,6 +7,7 @@
 #include "base/auto_reset.h"
 #include "base/bind.h"
 #include "base/location.h"
+#import "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/single_thread_task_runner.h"
@@ -24,6 +25,7 @@
 #include "ios/chrome/browser/pref_names.h"
 #import "ios/chrome/browser/signin/authentication_service_delegate.h"
 #import "ios/chrome/browser/signin/authentication_service_observer.h"
+#import "ios/chrome/browser/signin/signin_util.h"
 #include "ios/chrome/browser/sync/sync_setup_service.h"
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/ui/authentication/signin/signin_utils.h"
@@ -50,6 +52,18 @@
   LOGIN_METHOD_AND_SYNC_STATE_COUNT
 };
 
+// Enum for Signin.IOSDeviceRestoreSignedInState histogram.
+// Entries should not be renumbered and numeric values should never be reused.
+enum class IOSDeviceRestoreSignedinState : int {
+  // Case when the user is not signed in before the device restore.
+  kUserNotSignedInBeforeDeviceRestore = 0,
+  // Case when the user is signed in before the device restore but not after.
+  kUserSignedInBeforeDeviceRestoreAndSignedOutAfterDeviceRestore = 1,
+  // Case when the user is signed in before and after the device restore.
+  kUserSignedInBeforeAndAfterDeviceRestore = 2,
+  kMaxValue = kUserSignedInBeforeAndAfterDeviceRestore,
+};
+
 // A fake account id used in the list of last signed in accounts when migrating
 // an email for which the corresponding account was removed.
 constexpr char kFakeAccountIdForRemovedAccount[] = "0000000000000";
@@ -100,12 +114,18 @@
     std::unique_ptr<AuthenticationServiceDelegate> delegate) {
   CHECK(delegate);
   CHECK(!initialized());
+  bool has_primary_account_before_initialize =
+      identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin);
+  int account_count_before_initialize =
+      identity_manager_->GetAccountsWithRefreshTokens().size();
   delegate_ = std::move(delegate);
+  signin::Tribool device_restore_session = IsFirstSessionAfterDeviceRestore();
   initialized_ = true;
 
   MigrateAccountsStoredInPrefsIfNeeded();
 
-  HandleForgottenIdentity(nil, true /* should_prompt */);
+  HandleForgottenIdentity(nil, /*should_prompt=*/true,
+                          device_restore_session == signin::Tribool::kTrue);
 
   crash_keys::SetCurrentlySignedIn(
       HasPrimaryIdentity(signin::ConsentLevel::kSignin));
@@ -122,6 +142,33 @@
 
   identity_manager_observation_.Observe(identity_manager_);
   OnApplicationWillEnterForeground();
+  bool has_primary_account_after_initialize =
+      identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin);
+  DCHECK(!has_primary_account_after_initialize ||
+         has_primary_account_before_initialize);
+  if (device_restore_session == signin::Tribool::kTrue) {
+    // Records device restore histograms.
+    if (has_primary_account_before_initialize) {
+      base::UmaHistogramCounts100("Signin.IOSDeviceRestoreIdentityCountBefore",
+                                  account_count_before_initialize);
+      int account_count_after_initialize =
+          identity_manager_->GetAccountsWithRefreshTokens().size();
+      base::UmaHistogramCounts100("Signin.IOSDeviceRestoreIdentityCountAfter",
+                                  account_count_after_initialize);
+    }
+    IOSDeviceRestoreSignedinState signed_in_state =
+        IOSDeviceRestoreSignedinState::kUserNotSignedInBeforeDeviceRestore;
+    if (has_primary_account_before_initialize) {
+      signed_in_state =
+          has_primary_account_after_initialize
+              ? IOSDeviceRestoreSignedinState::
+                    kUserSignedInBeforeAndAfterDeviceRestore
+              : IOSDeviceRestoreSignedinState::
+                    kUserSignedInBeforeDeviceRestoreAndSignedOutAfterDeviceRestore;
+    }
+    base::UmaHistogramEnumeration("Signin.IOSDeviceRestoreSignedInState",
+                                  signed_in_state);
+  }
 }
 
 void AuthenticationService::Shutdown() {
@@ -528,8 +575,10 @@
   // might still be accessible in SSO, and |OnIdentityListChanged| will handle
   // this when |identity| will actually disappear from SSO.
   base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(&AuthenticationService::HandleForgottenIdentity,
-                                base::Unretained(this), identity, true));
+      FROM_HERE,
+      base::BindOnce(&AuthenticationService::HandleForgottenIdentity,
+                     base::Unretained(this), identity, /*should_prompt=*/true,
+                     /*device_restore=*/false));
 }
 
 void AuthenticationService::OnChromeIdentityServiceWillBeDestroyed() {
@@ -538,7 +587,8 @@
 
 void AuthenticationService::HandleForgottenIdentity(
     ChromeIdentity* invalid_identity,
-    bool should_prompt) {
+    bool should_prompt,
+    bool device_restore) {
   if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) {
     // User is not signed in. Nothing to do here.
     return;
@@ -563,9 +613,19 @@
                                        signin::ConsentLevel::kSync);
 
   // Metrics.
-  signin_metrics::ProfileSignout signout_source =
-      account_filtered_out ? signin_metrics::SIGNOUT_PREF_CHANGED
-                           : signin_metrics::ACCOUNT_REMOVED_FROM_DEVICE;
+  signin_metrics::ProfileSignout signout_source;
+  if (account_filtered_out) {
+    // Account filtered out by enterprise policy.
+    signout_source = signin_metrics::SIGNOUT_PREF_CHANGED;
+  } else if (device_restore) {
+    // Account removed from the device after a device restore.
+    signout_source =
+        signin_metrics::IOS_ACCOUNT_REMOVED_FROM_DEVICE_AFTER_RESTORE;
+  } else {
+    // Account removed from the device by another app or the token being
+    // invalid.
+    signout_source = signin_metrics::ACCOUNT_REMOVED_FROM_DEVICE;
+  }
 
   // Sign the user out.
   SignOut(signout_source, /*force_clear_browsing_data=*/false, nil);
@@ -584,7 +644,7 @@
 
   base::AutoReset<bool> auto_reset(&is_reloading_credentials_, true);
 
-  HandleForgottenIdentity(nil, keychain_reload);
+  HandleForgottenIdentity(nil, keychain_reload, /*device_restore=*/false);
   if (!HasPrimaryIdentity(signin::ConsentLevel::kSignin))
     return;
 
diff --git a/ios/chrome/browser/snapshots/BUILD.gn b/ios/chrome/browser/snapshots/BUILD.gn
index 8d98d23..7a2942d 100644
--- a/ios/chrome/browser/snapshots/BUILD.gn
+++ b/ios/chrome/browser/snapshots/BUILD.gn
@@ -32,7 +32,6 @@
     "//ios/chrome/browser/sessions:scene_util",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/util",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/third_party/webkit",
     "//ios/web/public",
@@ -75,7 +74,6 @@
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/ui/image_util",
     "//ios/chrome/browser/ui/util",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/web",
     "//ios/web/public/test",
     "//ios/web/public/test/fakes:fakes",
diff --git a/ios/chrome/browser/snapshots/snapshot_browser_agent.mm b/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
index a71e64b1e..73999da 100644
--- a/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
+++ b/ios/chrome/browser/snapshots/snapshot_browser_agent.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/sessions/scene_util.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -129,7 +128,7 @@
       [NSMutableSet setWithCapacity:web_state_list->count()];
   for (int index = 0; index < web_state_list->count(); ++index) {
     web::WebState* web_state = web_state_list->GetWebStateAt(index);
-    [tab_ids addObject:TabIdTabHelper::FromWebState(web_state)->tab_id()];
+    [tab_ids addObject:web_state->GetStableIdentifier()];
   }
   return tab_ids;
 }
diff --git a/ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.mm b/ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.mm
index b6b29ad..f2570d9 100644
--- a/ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.mm
+++ b/ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.mm
@@ -6,8 +6,8 @@
 
 #include "base/check.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/web/public/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
@@ -34,12 +34,12 @@
   NSMutableSet<NSString*>* set = [NSMutableSet set];
   if (active_index > 0) {
     web::WebState* web_state = web_state_list->GetWebStateAt(active_index - 1);
-    [set addObject:TabIdTabHelper::FromWebState(web_state)->tab_id()];
+    [set addObject:web_state->GetStableIdentifier()];
   }
 
   if (active_index + 1 < web_state_list->count()) {
     web::WebState* web_state = web_state_list->GetWebStateAt(active_index + 1);
-    [set addObject:TabIdTabHelper::FromWebState(web_state)->tab_id()];
+    [set addObject:web_state->GetStableIdentifier()];
   }
 
   snapshot_cache_.pinnedIDs = [set copy];
diff --git a/ios/chrome/browser/snapshots/snapshot_tab_helper_unittest.mm b/ios/chrome/browser/snapshots/snapshot_tab_helper_unittest.mm
index 96e974d..4178ce55 100644
--- a/ios/chrome/browser/snapshots/snapshot_tab_helper_unittest.mm
+++ b/ios/chrome/browser/snapshots/snapshot_tab_helper_unittest.mm
@@ -10,7 +10,6 @@
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/ui/image_util/image_util.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 #include "ios/web/public/test/web_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -351,9 +350,8 @@
 TEST_F(SnapshotTabHelperTest, ClosingWebStateDoesNotRemoveSnapshot) {
   id partialMock = OCMPartialMock(snapshot_cache_);
   auto web_state = std::make_unique<web::FakeWebState>();
-  TabIdTabHelper::CreateForWebState(web_state.get());
 
-  NSString* tab_id = TabIdTabHelper::FromWebState(web_state.get())->tab_id();
+  NSString* tab_id = web_state.get()->GetStableIdentifier();
   SnapshotTabHelper::CreateForWebState(web_state.get(), tab_id);
   [[partialMock reject] removeImageWithSnapshotID:tab_id];
 
diff --git a/ios/chrome/browser/tabs/tab_helper_util.mm b/ios/chrome/browser/tabs/tab_helper_util.mm
index 7cfcf1f..dac32d99 100644
--- a/ios/chrome/browser/tabs/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/tab_helper_util.mm
@@ -78,7 +78,6 @@
 #import "ios/chrome/browser/web/print/print_tab_helper.h"
 #import "ios/chrome/browser/web/sad_tab_tab_helper.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web/web_performance_metrics/web_performance_metrics_tab_helper.h"
 #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h"
 #import "ios/components/security_interstitials/lookalikes/lookalike_url_container.h"
@@ -89,9 +88,6 @@
 #import "ios/web/public/web_state.h"
 
 void AttachTabHelpers(web::WebState* web_state, bool for_prerender) {
-  // TabIdHelper sets up the tab ID.
-  TabIdTabHelper::CreateForWebState(web_state);
-
   ChromeBrowserState* browser_state =
       ChromeBrowserState::FromBrowserState(web_state->GetBrowserState());
 
@@ -99,7 +95,6 @@
   // so it needs to be created before them.
   IOSChromeSessionTabHelper::CreateForWebState(web_state);
 
-  NSString* tab_id = TabIdTabHelper::FromWebState(web_state)->tab_id();
   VoiceSearchNavigationTabHelper::CreateForWebState(web_state);
   IOSChromeSyncedTabDelegate::CreateForWebState(web_state);
   InfoBarManagerImpl::CreateForWebState(web_state);
@@ -200,7 +195,8 @@
   // condition can be removed.
   if (!for_prerender) {
     SadTabTabHelper::CreateForWebState(web_state);
-    SnapshotTabHelper::CreateForWebState(web_state, tab_id);
+    SnapshotTabHelper::CreateForWebState(web_state,
+                                         web_state->GetStableIdentifier());
     PagePlaceholderTabHelper::CreateForWebState(web_state);
     PrintTabHelper::CreateForWebState(web_state);
     InfobarBadgeTabHelper::CreateForWebState(web_state);
diff --git a/ios/chrome/browser/u2f/BUILD.gn b/ios/chrome/browser/u2f/BUILD.gn
index fd09a0e..9b0dd70 100644
--- a/ios/chrome/browser/u2f/BUILD.gn
+++ b/ios/chrome/browser/u2f/BUILD.gn
@@ -10,7 +10,6 @@
   deps = [
     ":u2f_internal",
     "//base",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/web",
     "//net",
     "//url",
@@ -49,7 +48,6 @@
     ":u2f_internal",
     "//base",
     "//ios/chrome/browser",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/web/public/deprecated",
     "//ios/web/public/test/fakes",
     "//net",
diff --git a/ios/chrome/browser/u2f/u2f_tab_helper.mm b/ios/chrome/browser/u2f/u2f_tab_helper.mm
index c18e200d..cb6dabd1 100644
--- a/ios/chrome/browser/u2f/u2f_tab_helper.mm
+++ b/ios/chrome/browser/u2f/u2f_tab_helper.mm
@@ -6,7 +6,6 @@
 
 #import "base/strings/sys_string_conversions.h"
 #import "ios/chrome/browser/u2f/u2f_controller.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/public/web_state.h"
 #include "net/base/url_util.h"
 
@@ -47,7 +46,7 @@
   // Create U2FController object lazily.
   if (!second_factor_controller_)
     second_factor_controller_ = [[U2FController alloc] init];
-  NSString* tab_id = TabIdTabHelper::FromWebState(web_state_)->tab_id();
+  NSString* tab_id = web_state_->GetStableIdentifier();
   return [second_factor_controller_
       XCallbackFromRequestURL:request_url
                     originURL:origin_url
diff --git a/ios/chrome/browser/u2f/u2f_tab_helper_unittest.mm b/ios/chrome/browser/u2f/u2f_tab_helper_unittest.mm
index 29fedce..c018019a 100644
--- a/ios/chrome/browser/u2f/u2f_tab_helper_unittest.mm
+++ b/ios/chrome/browser/u2f/u2f_tab_helper_unittest.mm
@@ -9,7 +9,6 @@
 #import "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
 #import "ios/chrome/browser/chrome_url_util.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/web/public/deprecated/url_verification_constants.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 #include "net/base/escape.h"
@@ -27,7 +26,6 @@
  protected:
   U2FTabHelperTest() {
     U2FTabHelper::CreateForWebState(&web_state_);
-    TabIdTabHelper::CreateForWebState(&web_state_);
     url::AddStandardScheme("chromium", url::SCHEME_WITH_HOST);
     [[ChromeAppConstants sharedInstance]
         setCallbackSchemeForTesting:@"chromium"];
@@ -35,10 +33,6 @@
 
   U2FTabHelper* tab_helper() { return U2FTabHelper::FromWebState(&web_state_); }
 
-  NSString* tab_id() {
-    return TabIdTabHelper::FromWebState(&web_state_)->tab_id();
-  }
-
   // Returns the requestUUID NSString from a properly formatted U2F XCallback
   // GURL.
   NSString* GetRequestUuidFromXCallbackUrl(const GURL& xcallback_url) {
@@ -66,9 +60,9 @@
   NSString* GetRegexString(const GURL& request_url, const GURL& origin_url) {
     return [@[
       @"u2f-x-callback://x-callback-url/auth\\?x-success=.+u2f-callback",
-      @"%2F%3FtabID%3D", tab_id(),
+      @"%2F%3FtabID%3D", web_state_.GetStableIdentifier(),
       @"%26requestUUID%3.+%26isU2F%3D1&x-error=.+u2f-callback%2F%3FtabID%3D",
-      tab_id(), @"%26requestUUID%3.+%26isU2F%3D1&data=",
+      web_state_.GetStableIdentifier(), @"%26requestUUID%3.+%26isU2F%3D1&data=",
       base::SysUTF8ToNSString(
           net::EscapeQueryParamValue(request_url.query(), true)),
       @"&origin=",
@@ -184,7 +178,7 @@
       "chromium://u2f-callback?requestUUID=" +
       base::SysNSStringToUTF8(request_uuid) +
       "&requestId=TestID&registrationData=TestData&tabID=" +
-      base::SysNSStringToUTF8(tab_id()));
+      base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
 
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
 
@@ -216,23 +210,24 @@
   GURL no_request_uuid_url(
       "chromium://"
       "u2f-callback?requestId=TestID&registrationData=TestData&tabID=" +
-      base::SysNSStringToUTF8(tab_id()));
+      base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
   tab_helper()->EvaluateU2FResult(no_request_uuid_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
 
   // Test when U2F callback has wrong requestUUID value.
-  GURL wrong_request_uuid_url("chromium://"
-                              "u2f-callback?requestId=TestID&registrationData="
-                              "TestData&requestUUID=123&tabID=" +
-                              base::SysNSStringToUTF8(tab_id()));
+  GURL wrong_request_uuid_url(
+      "chromium://"
+      "u2f-callback?requestId=TestID&registrationData="
+      "TestData&requestUUID=123&tabID=" +
+      base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
   tab_helper()->EvaluateU2FResult(wrong_request_uuid_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
 
   // Test when U2F callback has no registrationData value.
   GURL no_registration_request_url(
       "chromium://u2f-callback?requestUUID=" +
-      base::SysNSStringToUTF8(request_uuid) +
-      "&requestId=TestID&tabID=" + base::SysNSStringToUTF8(tab_id()));
+      base::SysNSStringToUTF8(request_uuid) + "&requestId=TestID&tabID=" +
+      base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
 
   tab_helper()->EvaluateU2FResult(no_registration_request_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
@@ -242,7 +237,7 @@
       "chromium://"
       "evil-callback?requestId=TestID&registrationData=TestData&requestUUID=" +
       base::SysNSStringToUTF8(request_uuid) +
-      "&tabID=" + base::SysNSStringToUTF8(tab_id()));
+      "&tabID=" + base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
   tab_helper()->EvaluateU2FResult(wrong_host_name_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
 }
@@ -266,7 +261,7 @@
       "chromium://"
       "u2f-callback?requestId=TestID&registrationData=TestData&requestUUID=" +
       base::SysNSStringToUTF8(request_uuid) +
-      "&tabID=" + base::SysNSStringToUTF8(tab_id()));
+      "&tabID=" + base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
 
   tab_helper()->EvaluateU2FResult(correct_request_uuid_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
@@ -280,7 +275,7 @@
       "chromium://"
       "u2f-callback?requestId=TestID&registrationData=TestData&requestUUID=" +
       base::SysNSStringToUTF8(request_uuid) +
-      "&tabID=" + base::SysNSStringToUTF8(tab_id()));
+      "&tabID=" + base::SysNSStringToUTF8(web_state_.GetStableIdentifier()));
 
   tab_helper()->EvaluateU2FResult(correct_request_uuid_url);
   EXPECT_TRUE(web_state_.GetLastExecutedJavascript().empty());
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 0cf1e5a7..24469c75 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -151,7 +151,6 @@
 #import "ios/chrome/browser/voice/voice_search_navigations_tab_helper.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
 #import "ios/chrome/browser/web/sad_tab_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 #import "ios/chrome/browser/web/web_navigation_util.h"
 #import "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h"
@@ -3210,8 +3209,7 @@
 - (BOOL)displaySignInNotification:(UIViewController*)viewController
                         fromTabId:(NSString*)tabId {
   // Check if the call comes from currently visible tab.
-  NSString* visibleTabId =
-      TabIdTabHelper::FromWebState(self.currentWebState)->tab_id();
+  NSString* visibleTabId = self.currentWebState->GetStableIdentifier();
   if ([tabId isEqual:visibleTabId]) {
     [self addChildViewController:viewController];
     [self.view addSubview:viewController.view];
@@ -4015,10 +4013,15 @@
 }
 
 - (void)lensControllerDidSelectURL:(NSURL*)URL {
+  // Dismiss the Lens view controller.
+  if (self.presentedViewController != nil) {
+    [self dismissViewControllerAnimated:YES completion:nil];
+  }
+
   // TODO(crbug.com/1234532): Integrate Lens with the browser's navigation
   // stack.
   UrlLoadParams loadParams = UrlLoadParams::InNewTab(net::GURLWithNSURL(URL));
-  loadParams.SetInBackground(YES);
+  loadParams.SetInBackground(NO);
   loadParams.in_incognito = self.isOffTheRecord;
   loadParams.append_to = kCurrentTab;
   UrlLoadingBrowserAgent* loadingAgent =
@@ -4071,8 +4074,7 @@
     self.browserContainerViewController.contentView = nil;
   }
 
-  [[UpgradeCenter sharedInstance]
-      tabWillClose:TabIdTabHelper::FromWebState(webState)->tab_id()];
+  [[UpgradeCenter sharedInstance] tabWillClose:webState->GetStableIdentifier()];
 }
 
 // Observer method, WebState replaced in |webStateList|.
@@ -4114,7 +4116,7 @@
   // infobar(s) that are just added.
   infobars::InfoBarManager* infoBarManager =
       InfoBarManagerImpl::FromWebState(webState);
-  NSString* tabID = TabIdTabHelper::FromWebState(webState)->tab_id();
+  NSString* tabID = webState->GetStableIdentifier();
   [[UpgradeCenter sharedInstance] addInfoBarToManager:infoBarManager
                                              forTabId:tabID];
   if (!ReSignInInfoBarDelegate::Create(self.browserState, webState,
diff --git a/ios/chrome/browser/ui/commerce/BUILD.gn b/ios/chrome/browser/ui/commerce/BUILD.gn
index 8e3917f..f11efdfd 100644
--- a/ios/chrome/browser/ui/commerce/BUILD.gn
+++ b/ios/chrome/browser/ui/commerce/BUILD.gn
@@ -18,7 +18,6 @@
     "price_card/resources:colors",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/commerce:commerce",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list:web_state_list",
     "//ios/chrome/common/ui/colors",
     "//ios/web",
diff --git a/ios/chrome/browser/ui/commerce/price_card/price_card_mediator.mm b/ios/chrome/browser/ui/commerce/price_card/price_card_mediator.mm
index ba6fcee..e368fcb 100644
--- a/ios/chrome/browser/ui/commerce/price_card/price_card_mediator.mm
+++ b/ios/chrome/browser/ui/commerce/price_card/price_card_mediator.mm
@@ -5,7 +5,6 @@
 #import "ios/chrome/browser/ui/commerce/price_card/price_card_mediator.h"
 
 #import "ios/chrome/browser/ui/commerce/price_card/price_card_item.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/web/public/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -29,8 +28,7 @@
 web::WebState* GetWebState(WebStateList* web_state_list, NSString* tab_id) {
   for (int i = 0; i < web_state_list->count(); i++) {
     web::WebState* web_state = web_state_list->GetWebStateAt(i);
-    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-    if ([tab_id isEqualToString:tab_helper->tab_id()])
+    if ([tab_id isEqualToString:web_state->GetStableIdentifier()])
       return web_state;
   }
   return nullptr;
diff --git a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
index cadcdcc..b4996b0 100644
--- a/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
+++ b/ios/chrome/browser/ui/side_swipe/side_swipe_controller.mm
@@ -26,7 +26,6 @@
 #include "ios/chrome/browser/ui/toolbar/public/side_swipe_toolbar_interacting.h"
 #import "ios/chrome/browser/ui/toolbar/public/side_swipe_toolbar_interacting.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web/web_navigation_util.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -361,7 +360,7 @@
     web::WebState* webState = self.webStateList->GetWebStateAt(index);
     if (webState && PagePlaceholderTabHelper::FromWebState(webState)
                         ->will_add_placeholder_for_next_navigation()) {
-      [sessionIDs addObject:TabIdTabHelper::FromWebState(webState)->tab_id()];
+      [sessionIDs addObject:webState->GetStableIdentifier()];
     }
     index = index + dx;
   }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/OWNERS b/ios/chrome/browser/ui/tab_switcher/tab_grid/OWNERS
index b98e1d4..03a9d2cf 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/OWNERS
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/OWNERS
@@ -1,2 +1,3 @@
 edchin@chromium.org
 marq@chromium.org
+mrefaat@chromium.org
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm
index c03a3a8..27320435 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator.mm
@@ -46,7 +46,6 @@
 #import "ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_item.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_item.h"
 #import "ios/chrome/browser/ui/util/url_with_title.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_serialization.h"
@@ -64,9 +63,8 @@
 namespace {
 // Constructs a TabSwitcherItem from a |web_state|.
 TabSwitcherItem* CreateItem(web::WebState* web_state) {
-  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-  TabSwitcherItem* item =
-      [[TabSwitcherItem alloc] initWithIdentifier:tab_helper->tab_id()];
+  TabSwitcherItem* item = [[TabSwitcherItem alloc]
+      initWithIdentifier:web_state->GetStableIdentifier()];
   // chrome://newtab (NTP) tabs have no title.
   if (IsURLNtp(web_state->GetVisibleURL())) {
     item.hidesTitle = YES;
@@ -94,8 +92,7 @@
   web::WebState* web_state = web_state_list->GetActiveWebState();
   if (!web_state)
     return nil;
-  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-  return tab_helper->tab_id();
+  return web_state->GetStableIdentifier();
 }
 
 void LogPriceDropMetrics(web::WebState* web_state) {
@@ -118,8 +115,7 @@
 int GetIndexOfTabWithId(WebStateList* web_state_list, NSString* identifier) {
   for (int i = 0; i < web_state_list->count(); i++) {
     web::WebState* web_state = web_state_list->GetWebStateAt(i);
-    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-    if ([identifier isEqualToString:tab_helper->tab_id()])
+    if ([identifier isEqualToString:web_state->GetStableIdentifier()])
       return i;
   }
   return WebStateList::kInvalidIndex;
@@ -131,8 +127,7 @@
                                  NSString* identifier) {
   for (int i = 0; i < web_state_list->count(); i++) {
     web::WebState* web_state = web_state_list->GetWebStateAt(i);
-    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-    if ([identifier isEqualToString:tab_helper->tab_id()])
+    if ([identifier isEqualToString:web_state->GetStableIdentifier()])
       return web_state;
   }
   return nullptr;
@@ -245,8 +240,8 @@
   DCHECK_EQ(_webStateList, webStateList);
   if (webStateList->IsBatchInProgress())
     return;
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
-  [self.consumer moveItemWithID:tabHelper->tab_id() toIndex:toIndex];
+  [self.consumer moveItemWithID:webState->GetStableIdentifier()
+                        toIndex:toIndex];
 }
 
 - (void)webStateList:(WebStateList*)webStateList
@@ -256,8 +251,7 @@
   DCHECK_EQ(_webStateList, webStateList);
   if (webStateList->IsBatchInProgress())
     return;
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(oldWebState);
-  [self.consumer replaceItemID:tabHelper->tab_id()
+  [self.consumer replaceItemID:oldWebState->GetStableIdentifier()
                       withItem:CreateItem(newWebState)];
   _scopedWebStateObservation->RemoveObservation(oldWebState);
   _scopedWebStateObservation->AddObservation(newWebState);
@@ -271,9 +265,7 @@
     return;
   if (!webStateList)
     return;
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
-  NSString* itemID = tabHelper->tab_id();
-  [self.consumer removeItemWithID:itemID
+  [self.consumer removeItemWithID:webState->GetStableIdentifier()
                    selectedItemID:GetActiveTabId(webStateList)];
   _scopedWebStateObservation->RemoveObservation(webState);
 }
@@ -293,8 +285,7 @@
     return;
   }
 
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(newWebState);
-  [self.consumer selectItemWithID:tabHelper->tab_id()];
+  [self.consumer selectItemWithID:newWebState->GetStableIdentifier()];
 }
 
 - (void)webStateListWillBeginBatchOperation:(WebStateList*)webStateList {
@@ -327,10 +318,8 @@
 }
 
 - (void)updateConsumerItemForWebState:(web::WebState*)webState {
-  // Assumption: the ID of the webState didn't change as a result of this load.
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
-  NSString* itemID = tabHelper->tab_id();
-  [self.consumer replaceItemID:itemID withItem:CreateItem(webState)];
+  [self.consumer replaceItemID:webState->GetStableIdentifier()
+                      withItem:CreateItem(webState)];
 }
 
 #pragma mark - SnapshotCacheObserver
@@ -698,7 +687,7 @@
                           self.webStateList->count() - 1);
   for (int i = startIndex; i <= endIndex; i++) {
     web::WebState* web_state = self.webStateList->GetWebStateAt(i);
-    NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
+    NSString* identifier = web_state->GetStableIdentifier();
     auto cacheImage = ^(UIImage* image) {
       self.appearanceCache[identifier] = image;
     };
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
index d96ca6c..2d10180 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/tab_grid_mediator_unittest.mm
@@ -37,7 +37,6 @@
 #import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/browser/web_state_list/fake_web_state_list_delegate.h"
 #include "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_opener.h"
@@ -247,13 +246,12 @@
 
   // WebStateListDelegate implementation.
   void WillAddWebState(web::WebState* web_state) override {
-    TabIdTabHelper::CreateForWebState(web_state);
     // Create NTPTabHelper to ensure VisibleURL is set to kChromeUINewTabURL.
     id delegate = OCMProtocolMock(@protocol(NewTabPageTabHelperDelegate));
     NewTabPageTabHelper::CreateForWebState(web_state);
     NewTabPageTabHelper::FromWebState(web_state)->SetDelegate(delegate);
     PagePlaceholderTabHelper::CreateForWebState(web_state);
-    NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
+    NSString* identifier = web_state->GetStableIdentifier();
     SnapshotTabHelper::CreateForWebState(web_state, identifier);
     WebSessionStateTabHelper::CreateForWebState(web_state);
   }
@@ -305,8 +303,7 @@
     // Insert some web states.
     for (int i = 0; i < 3; i++) {
       auto web_state = CreateFakeWebStateWithURL(GURL("https://foo/bar"));
-      NSString* identifier =
-          TabIdTabHelper::FromWebState(web_state.get())->tab_id();
+      NSString* identifier = web_state.get()->GetStableIdentifier();
       // Tab IDs should be unique.
       ASSERT_FALSE([identifiers containsObject:identifier]);
       [identifiers addObject:identifier];
@@ -317,8 +314,7 @@
     original_identifiers_ = [identifiers copy];
     web_state_list_->ActivateWebStateAt(1);
     original_selected_identifier_ =
-        TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(1))
-            ->tab_id();
+        web_state_list_->GetWebStateAt(1)->GetStableIdentifier();
     consumer_ = [[FakeConsumer alloc] init];
     mediator_ = [[TabGridMediator alloc] initWithConsumer:consumer_];
     mediator_.browser = browser_.get();
@@ -337,7 +333,6 @@
     web_state->SetNavigationManager(std::move(navigation_manager));
     web_state->SetBrowserState(browser_state_.get());
     web_state->SetCurrentURL(url);
-    TabIdTabHelper::CreateForWebState(web_state.get());
     SnapshotTabHelper::CreateForWebState(web_state.get(),
                                          [[NSUUID UUID] UUIDString]);
     return web_state;
@@ -404,9 +399,7 @@
 TEST_F(TabGridMediatorTest, ConsumerInsertItem) {
   ASSERT_EQ(3UL, consumer_.items.count);
   auto web_state = std::make_unique<web::FakeWebState>();
-  TabIdTabHelper::CreateForWebState(web_state.get());
-  NSString* item_identifier =
-      TabIdTabHelper::FromWebState(web_state.get())->tab_id();
+  NSString* item_identifier = web_state.get()->GetStableIdentifier();
   web_state_list_->InsertWebState(1, std::move(web_state),
                                   WebStateList::INSERT_FORCE_INDEX,
                                   WebStateOpener());
@@ -432,9 +425,8 @@
 TEST_F(TabGridMediatorTest, ConsumerUpdateSelectedItem) {
   EXPECT_NSEQ(original_selected_identifier_, consumer_.selectedItemID);
   web_state_list_->ActivateWebStateAt(2);
-  EXPECT_NSEQ(
-      TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(2))->tab_id(),
-      consumer_.selectedItemID);
+  EXPECT_NSEQ(web_state_list_->GetWebStateAt(2)->GetStableIdentifier(),
+              consumer_.selectedItemID);
 }
 
 // Tests that the consumer is notified when a web state is replaced.
@@ -442,9 +434,7 @@
 // id of the new item.
 TEST_F(TabGridMediatorTest, ConsumerReplaceItem) {
   auto new_web_state = std::make_unique<web::FakeWebState>();
-  TabIdTabHelper::CreateForWebState(new_web_state.get());
-  NSString* new_item_identifier =
-      TabIdTabHelper::FromWebState(new_web_state.get())->tab_id();
+  NSString* new_item_identifier = new_web_state->GetStableIdentifier();
   @autoreleasepool {
     web_state_list_->ReplaceWebStateAt(1, std::move(new_web_state));
   }
@@ -470,7 +460,7 @@
 TEST_F(TabGridMediatorTest, SelectItemCommand) {
   // Previous selected index is 1.
   NSString* identifier =
-      TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(2))->tab_id();
+      web_state_list_->GetWebStateAt(2)->GetStableIdentifier();
   [mediator_ selectItemWithID:identifier];
   EXPECT_EQ(2, web_state_list_->active_index());
   EXPECT_NSEQ(identifier, consumer_.selectedItemID);
@@ -482,7 +472,7 @@
 TEST_F(TabGridMediatorTest, CloseItemCommand) {
   // Previously there were 3 items.
   NSString* identifier =
-      TabIdTabHelper::FromWebState(web_state_list_->GetWebStateAt(0))->tab_id();
+      web_state_list_->GetWebStateAt(0)->GetStableIdentifier();
   [mediator_ closeItemWithID:identifier];
   EXPECT_EQ(2, web_state_list_->count());
   EXPECT_EQ(2UL, consumer_.items.count);
@@ -601,7 +591,7 @@
   // NavigationManager::GetVisibleURL requires WebState::IsLoading to be true
   // to return pending item's URL.
   EXPECT_EQ("", web_state->GetVisibleURL().spec());
-  NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
+  NSString* identifier = web_state->GetStableIdentifier();
   EXPECT_FALSE([original_identifiers_ containsObject:identifier]);
   // Consumer checks.
   EXPECT_EQ(4UL, consumer_.items.count);
@@ -627,7 +617,7 @@
   // NavigationManager::GetVisibleURL requires WebState::IsLoading to be true
   // to return pending item's URL.
   EXPECT_EQ("", web_state->GetVisibleURL().spec());
-  NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
+  NSString* identifier = web_state->GetStableIdentifier();
   EXPECT_FALSE([original_identifiers_ containsObject:identifier]);
   // Consumer checks.
   EXPECT_EQ(4UL, consumer_.items.count);
@@ -654,7 +644,7 @@
   NSMutableArray<NSString*>* pre_move_ids = [[NSMutableArray alloc] init];
   for (int i = 0; i < 3; i++) {
     web::WebState* web_state = web_state_list_->GetWebStateAt(i);
-    [pre_move_ids addObject:TabIdTabHelper::FromWebState(web_state)->tab_id()];
+    [pre_move_ids addObject:web_state->GetStableIdentifier()];
   }
   NSString* pre_move_selected_id =
       pre_move_ids[web_state_list_->active_index()];
@@ -670,7 +660,7 @@
   for (int index = 0; index < 2; index++) {
     web::WebState* web_state = web_state_list_->GetWebStateAt(index);
     ASSERT_TRUE(web_state);
-    NSString* identifier = TabIdTabHelper::FromWebState(web_state)->tab_id();
+    NSString* identifier = web_state->GetStableIdentifier();
     EXPECT_NSEQ(identifier, pre_move_ids[(index + 1) % 3]);
     EXPECT_NSEQ(identifier, consumer_.items[index]);
   }
@@ -683,8 +673,7 @@
   // No need to set a null price drop - it will be null by default. Simply
   // need to create the helper.
   ShoppingPersistedDataTabHelper::CreateForWebState(web_state_to_select);
-  [mediator_ selectItemWithID:TabIdTabHelper::FromWebState(web_state_to_select)
-                                  ->tab_id()];
+  [mediator_ selectItemWithID:web_state_to_select->GetStableIdentifier()];
   EXPECT_EQ(1, user_action_tester_.GetActionCount(kHasNoPriceDropUserAction));
   EXPECT_EQ(0, user_action_tester_.GetActionCount(kHasPriceDropUserAction));
 }
@@ -694,16 +683,14 @@
   web::WebState* web_state_to_select = web_state_list_->GetWebStateAt(2);
   ShoppingPersistedDataTabHelper::CreateForWebState(web_state_to_select);
   SetFakePriceDrop(web_state_to_select);
-  [mediator_ selectItemWithID:TabIdTabHelper::FromWebState(web_state_to_select)
-                                  ->tab_id()];
+  [mediator_ selectItemWithID:web_state_to_select->GetStableIdentifier()];
   EXPECT_EQ(1, user_action_tester_.GetActionCount(kHasPriceDropUserAction));
   EXPECT_EQ(0, user_action_tester_.GetActionCount(kHasNoPriceDropUserAction));
 }
 
 TEST_F(TabGridMediatorTest, TestSelectItemWithPriceDropExperimentOff) {
   web::WebState* web_state_to_select = web_state_list_->GetWebStateAt(2);
-  [mediator_ selectItemWithID:TabIdTabHelper::FromWebState(web_state_to_select)
-                                  ->tab_id()];
+  [mediator_ selectItemWithID:web_state_to_select->GetStableIdentifier()];
   EXPECT_EQ(0, user_action_tester_.GetActionCount(kHasNoPriceDropUserAction));
   EXPECT_EQ(0, user_action_tester_.GetActionCount(kHasPriceDropUserAction));
 }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_strip/BUILD.gn b/ios/chrome/browser/ui/tab_switcher/tab_strip/BUILD.gn
index 2a7acb6..35c02ef 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_strip/BUILD.gn
+++ b/ios/chrome/browser/ui/tab_switcher/tab_strip/BUILD.gn
@@ -42,7 +42,6 @@
     "//ios/chrome/browser/ui/image_util",
     "//ios/chrome/browser/ui/tab_switcher",
     "//ios/chrome/browser/ui/util",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/common/ui/colors",
     "//ios/web/public",
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.mm
index c783d6d..b528835 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_mediator.mm
@@ -11,7 +11,6 @@
 #import "ios/chrome/browser/tabs/tab_title_util.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_strip/tab_strip_consumer.h"
 #import "ios/chrome/browser/ui/tab_switcher/tab_switcher_item.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/all_web_state_observation_forwarder.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
@@ -28,9 +27,8 @@
 namespace {
 // Constructs a TabSwitcherItem from a |web_state|.
 TabSwitcherItem* CreateItem(web::WebState* web_state) {
-  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-  TabSwitcherItem* item =
-      [[TabSwitcherItem alloc] initWithIdentifier:tab_helper->tab_id()];
+  TabSwitcherItem* item = [[TabSwitcherItem alloc]
+      initWithIdentifier:web_state->GetStableIdentifier()];
   // chrome://newtab (NTP) tabs have no title.
   if (IsURLNtp(web_state->GetVisibleURL())) {
     item.hidesTitle = YES;
@@ -57,8 +55,7 @@
   web::WebState* web_state = web_state_list->GetActiveWebState();
   if (!web_state)
     return nil;
-  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-  return tab_helper->tab_id();
+  return web_state->GetStableIdentifier();
 }
 
 // Returns the WebState with |identifier| in |web_state_list|. Returns |nullptr|
@@ -67,8 +64,7 @@
                                  NSString* identifier) {
   for (int i = 0; i < web_state_list->count(); i++) {
     web::WebState* web_state = web_state_list->GetWebStateAt(i);
-    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-    if ([identifier isEqualToString:tab_helper->tab_id()])
+    if ([identifier isEqualToString:web_state->GetStableIdentifier()])
       return web_state;
   }
   return nullptr;
@@ -79,8 +75,7 @@
 int GetIndexOfTabWithId(WebStateList* web_state_list, NSString* identifier) {
   for (int i = 0; i < web_state_list->count(); i++) {
     web::WebState* web_state = web_state_list->GetWebStateAt(i);
-    TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(web_state);
-    if ([identifier isEqualToString:tab_helper->tab_id()])
+    if ([identifier isEqualToString:web_state->GetStableIdentifier()])
       return i;
   }
   return -1;
@@ -176,8 +171,7 @@
     return;
   }
 
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(newWebState);
-  [self.consumer selectItemWithID:tabHelper->tab_id()];
+  [self.consumer selectItemWithID:newWebState->GetStableIdentifier()];
 }
 
 #pragma mark - TabFaviconDataSource
@@ -260,10 +254,8 @@
 #pragma mark - CRWWebStateObserver
 
 - (void)webStateDidChangeTitle:(web::WebState*)webState {
-  // Assumption: the ID of the webState didn't change as a result of this load.
-  TabIdTabHelper* tabHelper = TabIdTabHelper::FromWebState(webState);
-  NSString* itemID = tabHelper->tab_id();
-  [self.consumer replaceItemID:itemID withItem:CreateItem(webState)];
+  [self.consumer replaceItemID:webState->GetStableIdentifier()
+                      withItem:CreateItem(webState)];
 }
 
 @end
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 43e3da810..8cb9674 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -72,7 +72,6 @@
     "//ui/base",
     "//url",
   ]
-  public_deps = [ ":tab_id_tab_helper" ]
 }
 
 source_set("delegate") {
@@ -121,19 +120,6 @@
   ]
 }
 
-source_set("tab_id_tab_helper") {
-  sources = [
-    "tab_id_tab_helper.h",
-    "tab_id_tab_helper.mm",
-  ]
-  deps = [
-    "//base",
-    "//ios/web/public",
-    "//ios/web/public/session",
-  ]
-  configs += [ "//build/config/compiler:enable_arc" ]
-}
-
 source_set("tab_helper_delegates") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
@@ -155,7 +141,6 @@
     "page_placeholder_tab_helper_unittest.mm",
     "repost_form_tab_helper_unittest.mm",
     "sad_tab_tab_helper_unittest.mm",
-    "tab_id_tab_helper_unittest.mm",
     "web_navigation_browser_agent_unittest.mm",
     "web_navigation_util_unittest.mm",
     "web_state_delegate_browser_agent_unittest.mm",
diff --git a/ios/chrome/browser/web/session_state/BUILD.gn b/ios/chrome/browser/web/session_state/BUILD.gn
index 033d223..b3f5ffe 100644
--- a/ios/chrome/browser/web/session_state/BUILD.gn
+++ b/ios/chrome/browser/web/session_state/BUILD.gn
@@ -25,7 +25,6 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/web:feature_flags",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/public/provider/chrome/browser",
     "//ios/web/common:features",
diff --git a/ios/chrome/browser/web/session_state/web_session_state_cache.mm b/ios/chrome/browser/web/session_state/web_session_state_cache.mm
index 8b3b345..5416e3ab0 100644
--- a/ios/chrome/browser/web/session_state/web_session_state_cache.mm
+++ b/ios/chrome/browser/web/session_state/web_session_state_cache.mm
@@ -26,7 +26,6 @@
 #import "ios/chrome/browser/main/browser_list.h"
 #import "ios/chrome/browser/main/browser_list_factory.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_tab_helper.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -139,7 +138,7 @@
 - (void)persistSessionStateData:(NSData*)data
                     forWebState:(const web::WebState*)webState {
   DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker);
-  NSString* sessionID = TabIdTabHelper::FromWebState(webState)->tab_id();
+  NSString* sessionID = webState->GetStableIdentifier();
   if (!data || !sessionID || !_taskRunner)
     return;
 
@@ -153,7 +152,7 @@
 }
 
 - (NSData*)sessionStateDataForWebState:(const web::WebState*)webState {
-  NSString* sessionID = TabIdTabHelper::FromWebState(webState)->tab_id();
+  NSString* sessionID = webState->GetStableIdentifier();
   base::FilePath filePath =
       _cacheDirectory.Append(base::SysNSStringToUTF8(sessionID));
   NSString* filePathString = base::SysUTF8ToNSString(filePath.AsUTF8Unsafe());
@@ -180,7 +179,7 @@
     return;
   }
 
-  NSString* sessionID = TabIdTabHelper::FromWebState(webState)->tab_id();
+  NSString* sessionID = webState->GetStableIdentifier();
 
   base::FilePath filePath =
       _cacheDirectory.Append(base::SysNSStringToUTF8(sessionID));
@@ -211,8 +210,7 @@
     WebStateList* webStateList = browser->GetWebStateList();
     for (int index = 0; index < webStateList->count(); ++index) {
       web::WebState* webState = webStateList->GetWebStateAt(index);
-      [liveSessionIDs
-          addObject:TabIdTabHelper::FromWebState(webState)->tab_id()];
+      [liveSessionIDs addObject:webState->GetStableIdentifier()];
     }
   }
 
@@ -220,8 +218,7 @@
     WebStateList* webStateList = browser->GetWebStateList();
     for (int index = 0; index < webStateList->count(); ++index) {
       web::WebState* webState = webStateList->GetWebStateAt(index);
-      [liveSessionIDs
-          addObject:TabIdTabHelper::FromWebState(webState)->tab_id()];
+      [liveSessionIDs addObject:webState->GetStableIdentifier()];
     }
   }
   return liveSessionIDs;
diff --git a/ios/chrome/browser/web/session_state/web_session_state_cache_unittest.mm b/ios/chrome/browser/web/session_state/web_session_state_cache_unittest.mm
index 35d85e4..4009d6a 100644
--- a/ios/chrome/browser/web/session_state/web_session_state_cache_unittest.mm
+++ b/ios/chrome/browser/web/session_state/web_session_state_cache_unittest.mm
@@ -12,7 +12,6 @@
 #include "base/task/thread_pool/thread_pool_instance.h"
 #import "base/test/ios/wait_util.h"
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/web/public/test/web_task_environment.h"
 #import "ios/web/public/web_state.h"
 #include "testing/platform_test.h"
@@ -38,15 +37,13 @@
 
     web::WebState::CreateParams createParams(chrome_browser_state_.get());
     web_state_ = web::WebState::Create(createParams);
-    TabIdTabHelper::CreateForWebState(web_state_.get());
 
     session_cache_directory_ = chrome_browser_state_->GetStatePath().Append(
         kWebSessionCacheDirectoryName);
   }
 
   bool StorageExists() {
-    NSString* sessionID =
-        TabIdTabHelper::FromWebState(web_state_.get())->tab_id();
+    NSString* sessionID = web_state_.get()->GetStableIdentifier();
     base::FilePath filePath =
         session_cache_directory_.Append(base::SysNSStringToUTF8(sessionID));
     return base::PathExists(filePath);
diff --git a/ios/chrome/browser/web/session_state/web_session_state_tab_helper.mm b/ios/chrome/browser/web/session_state/web_session_state_tab_helper.mm
index 3cb84f3..80834e2f 100644
--- a/ios/chrome/browser/web/session_state/web_session_state_tab_helper.mm
+++ b/ios/chrome/browser/web/session_state/web_session_state_tab_helper.mm
@@ -23,7 +23,6 @@
 #include "ios/chrome/browser/web/features.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_cache.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_cache_factory.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/web/common/features.h"
 #import "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/public/navigation/navigation_manager.h"
diff --git a/ios/chrome/browser/web/session_state/web_session_state_tab_helper_unittest.mm b/ios/chrome/browser/web/session_state/web_session_state_tab_helper_unittest.mm
index 13d096d..ab2d77b 100644
--- a/ios/chrome/browser/web/session_state/web_session_state_tab_helper_unittest.mm
+++ b/ios/chrome/browser/web/session_state/web_session_state_tab_helper_unittest.mm
@@ -20,7 +20,6 @@
 #include "ios/chrome/browser/web/chrome_web_test.h"
 #include "ios/chrome/browser/web/features.h"
 #import "ios/chrome/browser/web/session_state/web_session_state_cache.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/web/public/navigation/navigation_item.h"
 #include "ios/web/public/navigation/navigation_manager.h"
 #import "ios/web/public/session/serializable_user_data_manager.h"
@@ -45,7 +44,6 @@
   void SetUp() override {
     ChromeWebTest::SetUp();
 
-    TabIdTabHelper::CreateForWebState(web_state());
     WebSessionStateTabHelper::CreateForWebState(web_state());
 
     session_cache_directory_ =
@@ -86,7 +84,7 @@
   FlushRunLoops();
 
   // File should not be saved.
-  NSString* sessionID = TabIdTabHelper::FromWebState(web_state())->tab_id();
+  NSString* sessionID = web_state()->GetStableIdentifier();
   base::FilePath filePath =
       session_cache_directory_.Append(base::SysNSStringToUTF8(sessionID));
   ASSERT_FALSE(base::PathExists(filePath));
@@ -114,7 +112,7 @@
   base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(2));
 
   // File should not be saved yet.
-  NSString* sessionID = TabIdTabHelper::FromWebState(web_state())->tab_id();
+  NSString* sessionID = web_state()->GetStableIdentifier();
   base::FilePath filePath =
       session_cache_directory_.Append(base::SysNSStringToUTF8(sessionID));
   ASSERT_FALSE(base::PathExists(filePath));
@@ -138,7 +136,6 @@
   std::unique_ptr<web::WebState> web_state =
       web::WebState::Create(createParams);
   WebSessionStateTabHelper::CreateForWebState(web_state.get());
-  TabIdTabHelper::CreateForWebState(web_state.get());
   web_state->GetView();
   web_state->SetKeepRenderProcessAlive(true);
   GURL urlBlank("about:blank");
@@ -146,8 +143,7 @@
   web_state->GetNavigationManager()->LoadURLWithParams(paramsBlank);
 
   // copy the tabid file over to the new tabid...
-  NSString* newSessionID =
-      TabIdTabHelper::FromWebState(web_state.get())->tab_id();
+  NSString* newSessionID = web_state.get()->GetStableIdentifier();
   base::FilePath newFilePath =
       session_cache_directory_.Append(base::SysNSStringToUTF8(newSessionID));
   EXPECT_TRUE(base::CopyFile(filePath, newFilePath));
diff --git a/ios/chrome/browser/web/tab_id_tab_helper.h b/ios/chrome/browser/web/tab_id_tab_helper.h
deleted file mode 100644
index 4c8dd73..0000000
--- a/ios/chrome/browser/web/tab_id_tab_helper.h
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_WEB_TAB_ID_TAB_HELPER_H_
-#define IOS_CHROME_BROWSER_WEB_TAB_ID_TAB_HELPER_H_
-
-#import "ios/web/public/web_state_user_data.h"
-
-// Handles creating a unique identifier, which is stable across cold starts.
-//
-// TODO(crbug.com/1276776): Remove this class once all code has been converted
-// to instead use WebState::GetStableIdentifier() instead.
-class TabIdTabHelper : public web::WebStateUserData<TabIdTabHelper> {
- public:
-  TabIdTabHelper(const TabIdTabHelper&) = delete;
-  TabIdTabHelper& operator=(const TabIdTabHelper&) = delete;
-
-  ~TabIdTabHelper() override;
-
-  // Returns a unique identifier for this tab.
-  NSString* tab_id() const;
-
- private:
-  friend class web::WebStateUserData<TabIdTabHelper>;
-
-  explicit TabIdTabHelper(web::WebState* web_state);
-  web::WebState* web_state_ = nullptr;
-
-  WEB_STATE_USER_DATA_KEY_DECL();
-};
-
-#endif  // IOS_CHROME_BROWSER_WEB_TAB_ID_TAB_HELPER_H_
diff --git a/ios/chrome/browser/web/tab_id_tab_helper.mm b/ios/chrome/browser/web/tab_id_tab_helper.mm
deleted file mode 100644
index b01dc95..0000000
--- a/ios/chrome/browser/web/tab_id_tab_helper.mm
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
-
-#include "base/mac/foundation_util.h"
-#include "base/strings/sys_string_conversions.h"
-#import "ios/web/public/session/serializable_user_data_manager.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-TabIdTabHelper::TabIdTabHelper(web::WebState* web_state)
-    : web_state_(web_state) {
-  DCHECK(web_state_);
-}
-
-TabIdTabHelper::~TabIdTabHelper() = default;
-
-NSString* TabIdTabHelper::tab_id() const {
-  return web_state_->GetStableIdentifier();
-}
-
-WEB_STATE_USER_DATA_KEY_IMPL(TabIdTabHelper)
diff --git a/ios/chrome/browser/web/tab_id_tab_helper_unittest.mm b/ios/chrome/browser/web/tab_id_tab_helper_unittest.mm
deleted file mode 100644
index ed9e3f0..0000000
--- a/ios/chrome/browser/web/tab_id_tab_helper_unittest.mm
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
-
-#import "ios/web/public/test/fakes/fake_web_state.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#import "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-// Test fixture for TabIdTabHelper class.
-class TabIdTabHelperTest : public PlatformTest {
- protected:
-  web::FakeWebState first_web_state_;
-  web::FakeWebState second_web_state_;
-};
-
-// Tests that a tab ID is returned for a WebState, and tab ID's are different
-// for different WebStates.
-TEST_F(TabIdTabHelperTest, UniqueIdentifiers) {
-  TabIdTabHelper::CreateForWebState(&first_web_state_);
-  TabIdTabHelper::CreateForWebState(&second_web_state_);
-
-  const NSString* first_tab_id =
-      TabIdTabHelper::FromWebState(&first_web_state_)->tab_id();
-  const NSString* second_tab_id =
-      TabIdTabHelper::FromWebState(&second_web_state_)->tab_id();
-
-  EXPECT_GT([first_tab_id length], 0U);
-  EXPECT_GT([second_tab_id length], 0U);
-  EXPECT_NSNE(first_tab_id, second_tab_id);
-}
-
-// Tests that a tab ID is stable across successive calls.
-TEST_F(TabIdTabHelperTest, StableAcrossCalls) {
-  TabIdTabHelper::CreateForWebState(&first_web_state_);
-  TabIdTabHelper* tab_helper = TabIdTabHelper::FromWebState(&first_web_state_);
-
-  const NSString* first_call_id = tab_helper->tab_id();
-  const NSString* second_call_id = tab_helper->tab_id();
-
-  EXPECT_NSEQ(first_call_id, second_call_id);
-}
-
-// Tests serialization of a tab ID.
-TEST_F(TabIdTabHelperTest, Serialization) {
-  TabIdTabHelper::CreateForWebState(&first_web_state_);
-  TabIdTabHelper* first_tab_helper =
-      TabIdTabHelper::FromWebState(&first_web_state_);
-  const NSString* first_call_id = first_tab_helper->tab_id();
-
-  first_tab_helper->RemoveFromWebState(&first_web_state_);
-
-  TabIdTabHelper::CreateForWebState(&first_web_state_);
-  TabIdTabHelper* second_tab_helper =
-      TabIdTabHelper::FromWebState(&first_web_state_);
-  const NSString* second_call_id = second_tab_helper->tab_id();
-
-  EXPECT_NSEQ(first_call_id, second_call_id);
-}
diff --git a/ios/chrome/browser/web_state_list/BUILD.gn b/ios/chrome/browser/web_state_list/BUILD.gn
index 8e6c961a..881853a5 100644
--- a/ios/chrome/browser/web_state_list/BUILD.gn
+++ b/ios/chrome/browser/web_state_list/BUILD.gn
@@ -108,6 +108,7 @@
     "session_metrics_unittest.cc",
     "tab_insertion_browser_agent_unittest.mm",
     "web_state_dependency_installation_observer_unittest.mm",
+    "web_state_dependency_installer_bridge_unittest.mm",
     "web_state_list_favicon_driver_observer_unittest.mm",
     "web_state_list_order_controller_unittest.mm",
     "web_state_list_serialization_unittest.mm",
diff --git a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h
index df419c31..e96ded3 100644
--- a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h
+++ b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h
@@ -5,9 +5,12 @@
 #ifndef IOS_CHROME_BROWSER_WEB_STATE_LIST_WEB_STATE_DEPENDENCY_INSTALLATION_OBSERVER_H_
 #define IOS_CHROME_BROWSER_WEB_STATE_LIST_WEB_STATE_DEPENDENCY_INSTALLATION_OBSERVER_H_
 
+#import "base/scoped_multi_source_observation.h"
 #import "base/scoped_observation.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
+#import "ios/web/public/web_state.h"
+#import "ios/web/public/web_state_observer.h"
 
 // Interface for classes wishing to install and/or uninstall dependencies
 // (delegates, etc) for each WebState using
@@ -28,7 +31,8 @@
 // configured to do the installing/uninstalling work. This class acts as a
 // forwarder, listening for changes in the WebStateList and invoking the
 // installation/uninstallation methods as necessary.
-class WebStateDependencyInstallationObserver : public WebStateListObserver {
+class WebStateDependencyInstallationObserver : public WebStateListObserver,
+                                               public web::WebStateObserver {
  public:
   WebStateDependencyInstallationObserver(
       WebStateList* web_state_list,
@@ -54,6 +58,16 @@
       const WebStateDependencyInstallationObserver&) = delete;
 
  private:
+  // Helper methods that call InstallDependency/UninstallDependency on the
+  // `dependency_installer_` if the WebState is realized, or start observing
+  // the WebState for `WebStateRealized()` event.
+  void OnWebStateAdded(web::WebState* web_state);
+  void OnWebStateRemoved(web::WebState* web_state);
+
+  // web::WebStateObserver:
+  void WebStateRealized(web::WebState* web_state) override;
+  void WebStateDestroyed(web::WebState* web_state) override;
+
   // The WebStateList being observed for addition, replacement, and detachment
   // of WebStates
   WebStateList* web_state_list_;
@@ -61,8 +75,11 @@
   // the WebStateList
   DependencyInstaller* dependency_installer_;
   // Automatically detaches |this| from the WebStateList when destroyed
-  base::ScopedObservation<WebStateList, WebStateListObserver> observation_{
-      this};
+  base::ScopedObservation<WebStateList, WebStateListObserver>
+      web_state_list_observation_{this};
+  // Automatically detaches |this| from the WebStates when destroyed.
+  base::ScopedMultiSourceObservation<web::WebState, web::WebStateObserver>
+      web_state_observations_{this};
 };
 
 #endif  // IOS_CHROME_BROWSER_WEB_STATE_LIST_WEB_STATE_DEPENDENCY_INSTALLATION_OBSERVER_H_
diff --git a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.mm b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.mm
index 4f30012d..a7272d88 100644
--- a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.mm
+++ b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.mm
@@ -2,32 +2,41 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import "ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h"
+
+#import "base/check.h"
+
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
-#import "ios/chrome/browser/web_state_list/web_state_dependency_installation_observer.h"
-
 WebStateDependencyInstallationObserver::WebStateDependencyInstallationObserver(
     WebStateList* web_state_list,
     DependencyInstaller* dependency_installer)
     : web_state_list_(web_state_list),
       dependency_installer_(dependency_installer) {
-  observation_.Observe(web_state_list_);
+  DCHECK(web_state_list_);
+  DCHECK(dependency_installer_);
+
+  web_state_list_observation_.Observe(web_state_list_);
   for (int i = 0; i < web_state_list_->count(); i++) {
-    dependency_installer_->InstallDependency(web_state_list_->GetWebStateAt(i));
+    OnWebStateAdded(web_state_list_->GetWebStateAt(i));
   }
 }
 
 WebStateDependencyInstallationObserver::
-    ~WebStateDependencyInstallationObserver() {}
+    ~WebStateDependencyInstallationObserver() {
+  for (int i = 0; i < web_state_list_->count(); i++) {
+    OnWebStateRemoved(web_state_list_->GetWebStateAt(i));
+  }
+}
 
 void WebStateDependencyInstallationObserver::WebStateInsertedAt(
     WebStateList* web_state_list,
     web::WebState* web_state,
     int index,
     bool activating) {
-  dependency_installer_->InstallDependency(web_state);
+  OnWebStateAdded(web_state);
 }
 
 void WebStateDependencyInstallationObserver::WebStateReplacedAt(
@@ -35,13 +44,42 @@
     web::WebState* old_web_state,
     web::WebState* new_web_state,
     int index) {
-  dependency_installer_->UninstallDependency(old_web_state);
-  dependency_installer_->InstallDependency(new_web_state);
+  OnWebStateRemoved(old_web_state);
+  OnWebStateAdded(new_web_state);
 }
 
 void WebStateDependencyInstallationObserver::WebStateDetachedAt(
     WebStateList* web_state_list,
     web::WebState* web_state,
     int index) {
-  dependency_installer_->UninstallDependency(web_state);
+  OnWebStateRemoved(web_state);
+}
+
+void WebStateDependencyInstallationObserver::OnWebStateAdded(
+    web::WebState* web_state) {
+  if (web_state->IsRealized()) {
+    dependency_installer_->InstallDependency(web_state);
+  } else if (!web_state_observations_.IsObservingSource(web_state)) {
+    web_state_observations_.AddObservation(web_state);
+  }
+}
+
+void WebStateDependencyInstallationObserver::OnWebStateRemoved(
+    web::WebState* web_state) {
+  if (web_state->IsRealized()) {
+    dependency_installer_->UninstallDependency(web_state);
+  } else if (web_state_observations_.IsObservingSource(web_state)) {
+    web_state_observations_.RemoveObservation(web_state);
+  }
+}
+
+void WebStateDependencyInstallationObserver::WebStateRealized(
+    web::WebState* web_state) {
+  web_state_observations_.RemoveObservation(web_state);
+  OnWebStateAdded(web_state);
+}
+
+void WebStateDependencyInstallationObserver::WebStateDestroyed(
+    web::WebState* web_state) {
+  web_state_observations_.RemoveObservation(web_state);
 }
diff --git a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer_unittest.mm b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer_unittest.mm
index bae2e9a..3e42213 100644
--- a/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer_unittest.mm
+++ b/ios/chrome/browser/web_state_list/web_state_dependency_installation_observer_unittest.mm
@@ -30,11 +30,19 @@
   }
   ~FakeDependencyInstaller() override {}
 
-  bool WasInstalled(web::WebState* web_state) {
+  bool WasInstalled(web::WebState* web_state) const {
+    return installed_.count(web_state) > 0;
+  }
+
+  bool WasUninstalled(web::WebState* web_state) const {
+    return uninstalled_.count(web_state) > 0;
+  }
+
+  size_t InstallCount(web::WebState* web_state) const {
     return installed_.count(web_state);
   }
 
-  bool WasUninstalled(web::WebState* web_state) {
+  size_t UninstallCount(web::WebState* web_state) const {
     return uninstalled_.count(web_state);
   }
 
@@ -94,3 +102,61 @@
                                                   &installer_);
   EXPECT_TRUE(installer_.WasInstalled(web_state_raw));
 }
+
+// Verifies that unrealized web states don't get dependencies installed.
+TEST_F(WebStateDependencyInstallationObserverTest, UnrealizedWebStates) {
+  WebStateDependencyInstallationObserver observer(&web_state_list_,
+                                                  &installer_);
+  auto web_state_1 = std::make_unique<web::FakeWebState>();
+  web::WebState* web_state_1_raw = web_state_1.get();
+  web_state_list_.InsertWebState(0, std::move(web_state_1),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  EXPECT_TRUE(installer_.WasInstalled(web_state_1_raw));
+
+  auto web_state_2 = std::make_unique<web::FakeWebState>();
+  web_state_2->SetIsRealized(false);
+  web::WebState* web_state_2_raw = web_state_2.get();
+
+  // Insert the unrealized webstate but don't have it activate (since that
+  // forces realization).
+  web_state_list_.InsertWebState(1, std::move(web_state_2),
+                                 WebStateList::INSERT_NO_FLAGS,
+                                 WebStateOpener());
+  // The unrealized webstate should not have dependencies installed.
+  EXPECT_FALSE(installer_.WasInstalled(web_state_2_raw));
+  // Once realized, dependencioes should be installed.
+  web_state_2_raw->ForceRealized();
+  EXPECT_TRUE(installer_.WasInstalled(web_state_2_raw));
+
+  auto web_state_3 = std::make_unique<web::FakeWebState>();
+  web_state_3->SetIsRealized(false);
+  web::WebState* web_state_3_raw = web_state_3.get();
+
+  // Insert the unrealized webstate and activate it, forcing realization.
+  web_state_list_.InsertWebState(2, std::move(web_state_3),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  // The formerly unrealized webstate should have dependencies installed.
+  // (The webstate should also be realized, but that's not the responsibility
+  // of the code under test).
+  EXPECT_TRUE(installer_.WasInstalled(web_state_3_raw));
+  // Dependencies should have been installed only once
+  EXPECT_EQ(1u, installer_.InstallCount(web_state_3_raw));
+}
+
+// Verifies that destroying the observer triggers uninstallation.
+TEST_F(WebStateDependencyInstallationObserverTest, Disconnect) {
+  auto observer = std::make_unique<WebStateDependencyInstallationObserver>(
+      &web_state_list_, &installer_);
+  auto web_state_1 = std::make_unique<web::FakeWebState>();
+  web::WebState* web_state_1_raw = web_state_1.get();
+
+  web_state_list_.InsertWebState(0, std::move(web_state_1),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  EXPECT_TRUE(installer_.WasInstalled(web_state_1_raw));
+  observer.reset();
+
+  EXPECT_TRUE(installer_.WasUninstalled(web_state_1_raw));
+}
diff --git a/ios/chrome/browser/web_state_list/web_state_dependency_installer_bridge_unittest.mm b/ios/chrome/browser/web_state_list/web_state_dependency_installer_bridge_unittest.mm
new file mode 100644
index 0000000..7666c9a
--- /dev/null
+++ b/ios/chrome/browser/web_state_list/web_state_dependency_installer_bridge_unittest.mm
@@ -0,0 +1,82 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/web_state_list/web_state_dependency_installer_bridge.h"
+
+#import <memory>
+
+#import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/chrome/browser/web_state_list/web_state_list_delegate.h"
+#import "ios/chrome/browser/web_state_list/web_state_opener.h"
+#import "ios/web/public/test/fakes/fake_web_state.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Test object for tracking calls through the DpendencyInstalling protocol.
+@interface TestInstaller : NSObject <DependencyInstalling>
+
+@property(nonatomic) NSInteger installCount;
+@property(nonatomic) NSInteger uninstallCount;
+
+@end
+
+@implementation TestInstaller
+- (void)installDependencyForWebState:(web::WebState*)webState {
+  _installCount++;
+}
+- (void)uninstallDependencyForWebState:(web::WebState*)webState {
+  _uninstallCount++;
+}
+@end
+
+class WebStateDependencyInstallerBridgeTest : public PlatformTest,
+                                              public WebStateListDelegate {
+ public:
+  WebStateDependencyInstallerBridgeTest()
+      : web_state_list_(this), installer_([[TestInstaller alloc] init]) {}
+  // WebStateListDelegate.
+  void WillAddWebState(web::WebState* web_state) override {}
+  void WebStateDetached(web::WebState* web_state) override {}
+
+ protected:
+  WebStateList web_state_list_;
+  TestInstaller* installer_;
+};
+
+// Test that inserting and replacing web states calls the dependency installer
+// the expected number of times.
+TEST_F(WebStateDependencyInstallerBridgeTest, InsertReplaceAndRemoveWebState) {
+  WebStateDependencyInstallerBridge bridge(installer_, &web_state_list_);
+  auto web_state_1 = std::make_unique<web::FakeWebState>();
+  web_state_list_.InsertWebState(0, std::move(web_state_1),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  EXPECT_EQ(installer_.installCount, 1);
+  auto web_state_2 = std::make_unique<web::FakeWebState>();
+  web_state_list_.ReplaceWebStateAt(0, std::move(web_state_2));
+  EXPECT_EQ(installer_.installCount, 2);
+  EXPECT_EQ(installer_.uninstallCount, 1);
+}
+
+// Tests that deleting the installer bridge uninstalls all web states.
+TEST_F(WebStateDependencyInstallerBridgeTest, UninstallOnBridgeDestruction) {
+  auto bridge = std::make_unique<WebStateDependencyInstallerBridge>(
+      installer_, &web_state_list_);
+  auto web_state_1 = std::make_unique<web::FakeWebState>();
+  web_state_list_.InsertWebState(0, std::move(web_state_1),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  auto web_state_2 = std::make_unique<web::FakeWebState>();
+  web_state_list_.InsertWebState(0, std::move(web_state_2),
+                                 WebStateList::INSERT_ACTIVATE,
+                                 WebStateOpener());
+  EXPECT_EQ(installer_.installCount, 2);
+  EXPECT_EQ(installer_.uninstallCount, 0);
+  bridge.reset();
+  EXPECT_EQ(installer_.uninstallCount, 2);
+}
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 1b81cd1..e35af937 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -160,7 +160,6 @@
     "//ios/chrome/browser/variations:eg_app_support+eg2",
     "//ios/chrome/browser/web",
     "//ios/chrome/browser/web:eg_app_support+eg2",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/test/app:test_support",
     "//ios/chrome/test/variations_smoke_test:eg_app_support+eg2",
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index aadcba7..89b0768 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -35,7 +35,6 @@
 #import "ios/chrome/browser/ui/util/named_guide.h"
 #import "ios/chrome/browser/ui/util/rtl_geometry.h"
 #import "ios/chrome/browser/unified_consent/unified_consent_service_factory.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web/web_navigation_browser_agent.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/test/app/bookmarks_test_util.h"
@@ -320,12 +319,12 @@
 
 + (NSString*)currentTabID {
   web::WebState* web_state = chrome_test_util::GetCurrentWebState();
-  return TabIdTabHelper::FromWebState(web_state)->tab_id();
+  return web_state->GetStableIdentifier();
 }
 
 + (NSString*)nextTabID {
   web::WebState* web_state = chrome_test_util::GetNextWebState();
-  return TabIdTabHelper::FromWebState(web_state)->tab_id();
+  return web_state->GetStableIdentifier();
 }
 
 + (NSUInteger)indexOfActiveNormalTab {
diff --git a/ios/chrome/test/wpt/BUILD.gn b/ios/chrome/test/wpt/BUILD.gn
index 783b538..18912a0 100644
--- a/ios/chrome/test/wpt/BUILD.gn
+++ b/ios/chrome/test/wpt/BUILD.gn
@@ -71,7 +71,6 @@
     "//ios/chrome/app:tests_hook",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/tabs:tabs",
-    "//ios/chrome/browser/web:tab_id_tab_helper",
     "//ios/chrome/browser/web_state_list",
     "//ios/chrome/test/app:test_support",
     "//ios/testing:nserror_support",
diff --git a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
index 77f5992..21ec43c1 100644
--- a/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
+++ b/ios/chrome/test/wpt/cwt_webdriver_app_interface.mm
@@ -16,7 +16,6 @@
 #include "base/values.h"
 #import "ios/chrome/app/main_controller.h"
 #import "ios/chrome/browser/main/browser.h"
-#import "ios/chrome/browser/web/tab_id_tab_helper.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
 #import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/app/settings_test_util.h"
@@ -25,6 +24,7 @@
 #import "ios/testing/nserror_util.h"
 #import "ios/web/public/test/navigation_test_util.h"
 #import "ios/web/public/ui/crw_web_view_proxy.h"
+#import "ios/web/public/web_state.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/image/image.h"
 
@@ -37,8 +37,7 @@
 namespace {
 
 NSString* GetIdForWebState(web::WebState* web_state) {
-  TabIdTabHelper::CreateForWebState(web_state);
-  return TabIdTabHelper::FromWebState(web_state)->tab_id();
+  return web_state->GetStableIdentifier();
 }
 
 WebStateList* GetCurrentWebStateList() {
diff --git a/ios/web/public/test/fakes/fake_web_state.h b/ios/web/public/test/fakes/fake_web_state.h
index cef9214..b39d727 100644
--- a/ios/web/public/test/fakes/fake_web_state.h
+++ b/ios/web/public/test/fakes/fake_web_state.h
@@ -102,6 +102,7 @@
 
   // Setters for test data.
   void SetBrowserState(BrowserState* browser_state);
+  void SetIsRealized(bool value);
   void SetJSInjectionReceiver(CRWJSInjectionReceiver* injection_receiver);
   void SetTitle(const std::u16string& title);
   void SetContentIsHTML(bool content_is_html);
@@ -159,6 +160,7 @@
   CRWJSInjectionReceiver* injection_receiver_ = nil;
   NSString* stable_identifier_ = nil;
   bool web_usage_enabled_ = true;
+  bool is_realized_ = true;
   bool is_loading_ = false;
   bool is_visible_ = false;
   bool is_crashed_ = false;
diff --git a/ios/web/public/test/fakes/fake_web_state.mm b/ios/web/public/test/fakes/fake_web_state.mm
index 2eb49c4..d132d022 100644
--- a/ios/web/public/test/fakes/fake_web_state.mm
+++ b/ios/web/public/test/fakes/fake_web_state.mm
@@ -74,11 +74,15 @@
 void FakeWebState::SetDelegate(WebStateDelegate* delegate) {}
 
 bool FakeWebState::IsRealized() const {
-  // FakeWebState cannot be unrealized.
-  return true;
+  return is_realized_;
 }
 
 WebState* FakeWebState::ForceRealized() {
+  if (!is_realized_) {
+    is_realized_ = true;
+    for (auto& observer : observers_)
+      observer.WebStateRealized(this);
+  }
   return this;
 }
 
@@ -248,6 +252,10 @@
   browser_state_ = browser_state;
 }
 
+void FakeWebState::SetIsRealized(bool value) {
+  is_realized_ = value;
+}
+
 void FakeWebState::SetJSInjectionReceiver(
     CRWJSInjectionReceiver* injection_receiver) {
   injection_receiver_ = injection_receiver;
diff --git a/media/BUILD.gn b/media/BUILD.gn
index 8bd9260..81e70ad 100644
--- a/media/BUILD.gn
+++ b/media/BUILD.gn
@@ -185,6 +185,10 @@
 
   data_deps = [ "//testing/buildbot/filters:media_unittests_filters" ]
 
+  if (is_android && enable_chrome_android_internal) {
+    data_deps += [ "//clank/build/bot/filters:media_unittests_filters" ]
+  }
+
   if (media_use_ffmpeg) {
     deps += [ "//media/ffmpeg:unit_tests" ]
   }
diff --git a/media/fuchsia/cdm/service/fuchsia_cdm_manager.cc b/media/fuchsia/cdm/service/fuchsia_cdm_manager.cc
index 861afb29..fe8551ba 100644
--- a/media/fuchsia/cdm/service/fuchsia_cdm_manager.cc
+++ b/media/fuchsia/cdm/service/fuchsia_cdm_manager.cc
@@ -276,8 +276,8 @@
   // CDM data directories that are in active use, the |storage_task_runner_| is
   // sequenced, thereby ensuring cleanup completes before any CDM activities
   // start.
-  if (cdm_data_quota_bytes)
-    ApplyCdmStorageQuota(cdm_data_path_, *cdm_data_quota_bytes);
+  if (cdm_data_quota_bytes_)
+    ApplyCdmStorageQuota(cdm_data_path_, *cdm_data_quota_bytes_);
 }
 
 FuchsiaCdmManager::~FuchsiaCdmManager() = default;
diff --git a/media/mojo/clients/win/media_foundation_renderer_client.cc b/media/mojo/clients/win/media_foundation_renderer_client.cc
index 85cc9c9..e895349 100644
--- a/media/mojo/clients/win/media_foundation_renderer_client.cc
+++ b/media/mojo/clients/win/media_foundation_renderer_client.cc
@@ -7,10 +7,9 @@
 #include <utility>
 
 #include "base/callback_helpers.h"
-#include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
 #include "media/base/media_log.h"
 #include "media/base/win/mf_helpers.h"
+#include "media/renderers/win/media_foundation_renderer.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 
 namespace media {
@@ -131,6 +130,8 @@
 
 void MediaFoundationRendererClient::OnError(PipelineStatus status) {
   DVLOG_FUNC(1) << "status=" << status;
+  // Do not call MediaFoundationRenderer::ReportErrorReason() since it should've
+  // already been reported in MediaFoundationRenderer.
   client_->OnError(status);
 }
 
@@ -300,6 +301,8 @@
     MEDIA_LOG(ERROR, media_log_)
         << "Failed to initialize DCOMP mode or failed to get or "
            "register DCOMP surface handle on remote renderer";
+    MediaFoundationRenderer::ReportErrorReason(
+        MediaFoundationRenderer::ErrorReason::kOnDCompSurfaceReceivedError);
     OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
     return;
   }
@@ -317,6 +320,8 @@
 
   if (!success) {
     MEDIA_LOG(ERROR, media_log_) << "Failed to set DCOMP surface handle";
+    MediaFoundationRenderer::ReportErrorReason(
+        MediaFoundationRenderer::ErrorReason::kOnDCompSurfaceHandleSetError);
     OnError(PIPELINE_ERROR_COULD_NOT_RENDER);
   }
 }
@@ -348,7 +353,9 @@
   DVLOG_FUNC(1);
   DCHECK(media_task_runner_->BelongsToCurrentThread());
   MEDIA_LOG(ERROR, media_log_) << "MediaFoundationRendererClient disconnected";
-  OnError(PIPELINE_ERROR_DECODE);
+  MediaFoundationRenderer::ReportErrorReason(
+      MediaFoundationRenderer::ErrorReason::kOnConnectionError);
+  OnError(PIPELINE_ERROR_DISCONNECTED);
 }
 
 }  // namespace media
diff --git a/media/renderers/win/media_foundation_renderer.cc b/media/renderers/win/media_foundation_renderer.cc
index 3102930..3180b36 100644
--- a/media/renderers/win/media_foundation_renderer.cc
+++ b/media/renderers/win/media_foundation_renderer.cc
@@ -70,6 +70,12 @@
 }  // namespace
 
 // static
+void MediaFoundationRenderer::ReportErrorReason(ErrorReason reason) {
+  base::UmaHistogramEnumeration("Media.MediaFoundationRenderer.ErrorReason",
+                                reason);
+}
+
+// static
 bool MediaFoundationRenderer::IsSupported() {
   return base::win::GetVersion() >= base::win::Version::WIN10;
 }
@@ -344,6 +350,7 @@
 
   if (!waiting_for_mf_cdm_ || !content_protection_manager_) {
     DLOG(ERROR) << "Failed in checking internal state.";
+    ReportErrorReason(ErrorReason::kCdmProxyReceivedInInvalidState);
     renderer_client_->OnError(PipelineStatus::PIPELINE_ERROR_INVALID_STATE);
     return;
   }
@@ -356,6 +363,7 @@
   HRESULT hr = SetSourceOnMediaEngine();
   if (FAILED(hr)) {
     DLOG(ERROR) << "Failed to set source on media engine: " << PrintHr(hr);
+    ReportErrorReason(ErrorReason::kFailedToSetSourceOnMediaEngine);
     renderer_client_->OnError(PipelineStatus::PIPELINE_ERROR_COULD_NOT_RENDER);
     return;
   }
@@ -390,6 +398,7 @@
   HRESULT hr = mf_media_engine_->SetCurrentTime(current_time);
   if (FAILED(hr)) {
     DLOG(ERROR) << "Failed to SetCurrentTime: " << PrintHr(hr);
+    ReportErrorReason(ErrorReason::kFailedToSetCurrentTime);
     renderer_client_->OnError(PipelineStatus::PIPELINE_ERROR_COULD_NOT_RENDER);
     return;
   }
@@ -397,6 +406,7 @@
   hr = mf_media_engine_->Play();
   if (FAILED(hr)) {
     DLOG(ERROR) << "Failed to start playback: " << PrintHr(hr);
+    ReportErrorReason(ErrorReason::kFailedToPlay);
     renderer_client_->OnError(PipelineStatus::PIPELINE_ERROR_COULD_NOT_RENDER);
     return;
   }
@@ -603,6 +613,7 @@
       << "MediaFoundationRenderer OnPlaybackError: " << status << ", "
       << PrintHr(hr);
 
+  ReportErrorReason(ErrorReason::kOnPlaybackError);
   renderer_client_->OnError(status);
   StopSendingStatistics();
 }
diff --git a/media/renderers/win/media_foundation_renderer.h b/media/renderers/win/media_foundation_renderer.h
index 6de4f02..64933657 100644
--- a/media/renderers/win/media_foundation_renderer.h
+++ b/media/renderers/win/media_foundation_renderer.h
@@ -40,6 +40,25 @@
     : public Renderer,
       public MediaFoundationRendererExtension {
  public:
+  // An enum for recording MediaFoundationRenderer playback error reason.
+  // Reported to UMA. Do not change existing values.
+  enum class ErrorReason {
+    kUnknown = 0,
+    kCdmProxyReceivedInInvalidState,
+    kFailedToSetSourceOnMediaEngine,
+    kFailedToSetCurrentTime,
+    kFailedToPlay,
+    kOnPlaybackError,
+    kOnDCompSurfaceReceivedError,
+    kOnDCompSurfaceHandleSetError,
+    kOnConnectionError,
+    // Add new values here and update `kMaxValue`. Never reuse existing values.
+    kMaxValue = kOnConnectionError,
+  };
+
+  // Report `reason` to UMA.
+  static void ReportErrorReason(ErrorReason reason);
+
   // Whether MediaFoundationRenderer() is supported on the current device.
   static bool IsSupported();
 
diff --git a/net/android/BUILD.gn b/net/android/BUILD.gn
index d8dbcb7..fa5e7af 100644
--- a/net/android/BUILD.gn
+++ b/net/android/BUILD.gn
@@ -11,6 +11,7 @@
     "java/src/org/chromium/net/AndroidKeyStore.java",
     "java/src/org/chromium/net/AndroidNetworkLibrary.java",
     "java/src/org/chromium/net/AndroidTrafficStats.java",
+    "java/src/org/chromium/net/ChromiumNetworkAdapter.java",
     "java/src/org/chromium/net/DnsStatus.java",
     "java/src/org/chromium/net/GURLUtils.java",
     "java/src/org/chromium/net/HttpNegotiateAuthenticator.java",
diff --git a/net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java b/net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java
new file mode 100644
index 0000000..5146449
--- /dev/null
+++ b/net/android/java/src/org/chromium/net/ChromiumNetworkAdapter.java
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.net;
+
+import java.io.IOException;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * Wrapper class for network requests.
+ */
+public final class ChromiumNetworkAdapter {
+    private ChromiumNetworkAdapter() {}
+
+    /**
+     * Wrapper around URL#openConnection(), with an extra argument for static analysis/privacy
+     * auditing.
+     *
+     * @param url the URL to open connection to.
+     * @param traffic_annotation an object documenting this network request: what it's used for,
+     *     what data gets sent, what triggers it, etc.
+     * @return a URLConnection linking to the URL.
+     */
+    public static URLConnection openConnection(
+            URL url, NetworkTrafficAnnotationTag trafficAnnotation) throws IOException {
+        return url.openConnection();
+    }
+
+    /**
+     * Wrapper around URL#openConnection(Proxy), with an extra argument for static analysis/privacy
+     * auditing.
+     *
+     * @param url the URL to open connection to.
+     * @param proxy the Proxy through which this connection will be made.
+     * @param traffic_annotation an object documenting this network request: what it's used for,
+     *     what data gets sent, what triggers it, etc.
+     * @return a URLConnection linking to the URL.
+     */
+    public static URLConnection openConnection(URL url, Proxy proxy,
+            NetworkTrafficAnnotationTag trafficAnnotation) throws IOException {
+        return url.openConnection(proxy);
+    }
+}
diff --git a/net/dns/mock_host_resolver.cc b/net/dns/mock_host_resolver.cc
index 5c43594c..3f679cc 100644
--- a/net/dns/mock_host_resolver.cc
+++ b/net/dns/mock_host_resolver.cc
@@ -1112,7 +1112,7 @@
       replacement(replacement),
       dns_aliases(std::move(dns_aliases)),
       latency_ms(latency_ms) {
-  DCHECK(dns_aliases != std::vector<std::string>({""}));
+  DCHECK(this->dns_aliases != std::vector<std::string>({""}));
 }
 
 RuleBasedHostResolverProc::Rule::Rule(const Rule& other) = default;
diff --git a/net/reporting/reporting_endpoint.cc b/net/reporting/reporting_endpoint.cc
index fe4a1bb..187c0c7 100644
--- a/net/reporting/reporting_endpoint.cc
+++ b/net/reporting/reporting_endpoint.cc
@@ -34,7 +34,8 @@
       origin(origin),
       group_name(group_name) {
   // If |reporting_source| is present, it must not be empty.
-  DCHECK(!(reporting_source.has_value() && reporting_source->is_empty()));
+  DCHECK(!(this->reporting_source.has_value() &&
+           this->reporting_source->is_empty()));
 }
 
 ReportingEndpointGroupKey::ReportingEndpointGroupKey(
diff --git a/pdf/pdfium/fuzzers/BUILD.gn b/pdf/pdfium/fuzzers/BUILD.gn
index b57f6b6a..7916e22 100644
--- a/pdf/pdfium/fuzzers/BUILD.gn
+++ b/pdf/pdfium/fuzzers/BUILD.gn
@@ -91,6 +91,7 @@
       deps += [
         ":pdf_xfa_fdp_fuzzer",
         ":pdf_xfa_raw_fuzzer",
+        ":pdf_xfa_xdp_fdp_fuzzer",
       ]
     }
   }
@@ -286,6 +287,9 @@
     pdfium_fuzzer_test("pdf_xfa_raw_fuzzer") {
       dict = "dicts/pdf_xfa_raw_fuzzer.dict"
     }
+    pdfium_fuzzer_test("pdf_xfa_xdp_fdp_fuzzer") {
+      dict = "dicts/pdf_xfa_raw_fuzzer.dict"
+    }
   }
 }
 
diff --git a/services/device/fingerprint/fingerprint_chromeos.cc b/services/device/fingerprint/fingerprint_chromeos.cc
index e0c6d86..04f7e344 100644
--- a/services/device/fingerprint/fingerprint_chromeos.cc
+++ b/services/device/fingerprint/fingerprint_chromeos.cc
@@ -56,6 +56,8 @@
       return device::mojom::ScanResult::TOO_FAST;
     case biod::SCAN_RESULT_IMMOBILE:
       return device::mojom::ScanResult::IMMOBILE;
+    case biod::SCAN_RESULT_NO_MATCH:
+      return device::mojom::ScanResult::NO_MATCH;
     case biod::SCAN_RESULT_MAX:
       return device::mojom::ScanResult::kMaxValue;
   }
@@ -244,12 +246,12 @@
     entries.emplace_back(std::move(item.first), std::move(paths));
   }
 
-  auto casted_scan_result = static_cast<device::mojom::ScanResult>(scan_result);
-  CHECK(device::mojom::IsKnownEnumValue(casted_scan_result));
+  auto mojom_scan_result = ToMojom(scan_result);
+  CHECK(device::mojom::IsKnownEnumValue(mojom_scan_result));
 
   for (auto& observer : observers_) {
     observer->OnAuthScanDone(
-        casted_scan_result,
+        mojom_scan_result,
         base::flat_map<std::string, std::vector<std::string>>(entries));
   }
 }
diff --git a/services/device/fingerprint/fingerprint_chromeos_unittest.cc b/services/device/fingerprint/fingerprint_chromeos_unittest.cc
index 85ca06a..34152fb 100644
--- a/services/device/fingerprint/fingerprint_chromeos_unittest.cc
+++ b/services/device/fingerprint/fingerprint_chromeos_unittest.cc
@@ -41,6 +41,7 @@
       const base::flat_map<std::string, std::vector<std::string>>& matches)
       override {
     auth_scan_dones_++;
+    last_scan_result_ = scan_result;
   }
 
   void OnSessionFailed() override { session_failures_++; }
@@ -51,12 +52,18 @@
   int restarts() { return restarts_; }
   int session_failures() { return session_failures_; }
 
+  device::mojom::ScanResult last_scan_result() const {
+    return last_scan_result_;
+  }
+
  private:
   mojo::Receiver<mojom::FingerprintObserver> receiver_;
   int enroll_scan_dones_ = 0;  // Count of enroll scan done signal received.
   int auth_scan_dones_ = 0;    // Count of auth scan done signal received.
   int restarts_ = 0;           // Count of restart signal received.
   int session_failures_ = 0;   // Count of session failed signal received.
+
+  device::mojom::ScanResult last_scan_result_;  // Last received ScanResult.
 };
 
 class FingerprintChromeOSTest : public testing::Test {
@@ -89,10 +96,10 @@
         -1 /* percent_complete */);
   }
 
-  void GenerateAuthScanDoneSignal() {
+  void GenerateAuthScanDoneSignal(biod::ScanResult scan_result) {
     std::string fake_fingerprint_data;
-    chromeos::FakeBiodClient::Get()->SendAuthScanDone(
-        fake_fingerprint_data, biod::SCAN_RESULT_SUCCESS);
+    chromeos::FakeBiodClient::Get()->SendAuthScanDone(fake_fingerprint_data,
+                                                      scan_result);
   }
 
   void GenerateSessionFailedSignal() {
@@ -162,7 +169,7 @@
   chromeos::FakeBiodClient::Get()->StartAuthSession(base::BindOnce(
       &FingerprintChromeOSTest::onStartSession, base::Unretained(this)));
   base::RunLoop().RunUntilIdle();
-  GenerateAuthScanDoneSignal();
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_SUCCESS);
   base::RunLoop().RunUntilIdle();
   EXPECT_EQ(observer.auth_scan_dones(), 1);
 
@@ -195,4 +202,59 @@
   EXPECT_TRUE(RequestDataIsReset());
 }
 
+TEST_F(FingerprintChromeOSTest, FingerprintScanResultConvertTest) {
+  mojo::PendingRemote<mojom::FingerprintObserver> pending_observer;
+  FakeFingerprintObserver observer(
+      pending_observer.InitWithNewPipeAndPassReceiver());
+  fingerprint()->AddFingerprintObserver(std::move(pending_observer));
+
+  chromeos::FakeBiodClient::Get()->StartAuthSession(base::BindOnce(
+      &FingerprintChromeOSTest::onStartSession, base::Unretained(this)));
+  base::RunLoop().RunUntilIdle();
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_SUCCESS);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::SUCCESS);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_PARTIAL);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::PARTIAL);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_INSUFFICIENT);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(),
+            device::mojom::ScanResult::INSUFFICIENT);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_SENSOR_DIRTY);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(),
+            device::mojom::ScanResult::SENSOR_DIRTY);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_TOO_SLOW);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::TOO_SLOW);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_TOO_FAST);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::TOO_FAST);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_IMMOBILE);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::IMMOBILE);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_NO_MATCH);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::NO_MATCH);
+
+  GenerateAuthScanDoneSignal(biod::SCAN_RESULT_MAX);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(observer.last_scan_result(), device::mojom::ScanResult::kMaxValue);
+}
+
+// Make sure that compilation fails if a new value is added and this assert is
+// not updated. When updating this, please extend unit tests to check newly
+// added value.
+static_assert(device::mojom::ScanResult::kMaxValue ==
+              device::mojom::ScanResult::NO_MATCH);
+
 }  // namespace device
diff --git a/services/device/nfc/README.md b/services/device/nfc/README.md
index 9c0a5a1..3c83d54 100644
--- a/services/device/nfc/README.md
+++ b/services/device/nfc/README.md
@@ -13,12 +13,16 @@
 implemented by the `services/device/nfc` module.
 
 NDEFReader is the primary interface of Web NFC. The NDEFReader interface has
-both, write and scan methods:
+write, makeReadOnly, and scan methods:
 
 - The write method is for writing data to an NFC tag. This method returns a
   promise, which will be resolved when the message is successfully written to an
   NFC tag, or rejected either when errors happened or process is aborted by
   setting the AbortSignal in the NDEFWriteOptions.
+- The makeReadOnly method is for making an NFC tag permanently read-only. This
+  method returns a promise, which will be resolved when an NFC tag has been made
+  read-only, or rejected either when errors happened or process is aborted by
+  setting the AbortSignal in the NDEFMakeReadOnlyOptions.
 - The scan method tries to read data from any NFC tag that comes within
   proximity. Once there is some data found, an NDEFReadingEvent carrying the
   data is dispatched to the NDEFReader.
diff --git a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
index 2e32fd7..5ae5aa3 100644
--- a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
+++ b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
@@ -37,7 +37,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
-/** Android implementation of the NFC mojo service defined in device/nfc/nfc.mojom.
+/**
+ * Android implementation of the NFC mojo service defined in
+ * services/device/public/mojom/nfc.mojom.
  */
 public class NfcImpl implements Nfc {
     private static final String TAG = "NfcImpl";
@@ -89,6 +91,13 @@
     private PendingPushOperation mPendingPushOperation;
 
     /**
+     * Object that contains the callback that was passed to method
+     * #makeReadOnly(MakeReadOnly_Response callback)
+     * @see PendingMakeReadOnlyOperation
+     */
+    private PendingMakeReadOnlyOperation mPendingMakeReadOnlyOperation;
+
+    /**
      * Utility that provides I/O operations for a Tag. Created on demand when Tag is found.
      * @see NfcTagHandler
      */
@@ -219,8 +228,43 @@
      */
     @Override
     public void cancelPush() {
-        completePendingPushOperation(createError(
-                NdefErrorType.OPERATION_CANCELLED, "The push operation is already cancelled."));
+        completePendingPushOperation(
+                createError(NdefErrorType.OPERATION_CANCELLED, "The push operation is cancelled."));
+    }
+
+    /**
+     * Make NFC tag read-only whenever it is in proximity.
+     *
+     * @param callback that is used to notify when make read-only operation is completed.
+     */
+    @Override
+    public void makeReadOnly(MakeReadOnly_Response callback) {
+        if (!checkIfReady(callback)) return;
+
+        if (mOperationsSuspended) {
+            callback.call(createError(NdefErrorType.OPERATION_CANCELLED,
+                    "Cannot make read-only because NFC operations are suspended."));
+        }
+
+        // If previous pending make read-only operation is not completed, cancel it.
+        if (mPendingMakeReadOnlyOperation != null) {
+            mPendingMakeReadOnlyOperation.complete(createError(NdefErrorType.OPERATION_CANCELLED,
+                    "Make read-only is cancelled due to a new make read-only request."));
+        }
+
+        mPendingMakeReadOnlyOperation = new PendingMakeReadOnlyOperation(callback);
+
+        enableReaderModeIfNeeded();
+        processPendingMakeReadOnlyOperation();
+    }
+
+    /**
+     * Cancels pending make read-only operation.
+     */
+    @Override
+    public void cancelMakeReadOnly() {
+        completePendingMakeReadOnlyOperation(createError(
+                NdefErrorType.OPERATION_CANCELLED, "The make read-only operation is cancelled."));
     }
 
     /**
@@ -274,16 +318,17 @@
     }
 
     /**
-     * Suspends all pending watch operations and cancel push operations.
+     * Suspends all pending watch operations and cancel push / makeReadOnly operations.
      */
     public void suspendNfcOperations() {
         mOperationsSuspended = true;
         disableReaderMode();
         cancelPush();
+        cancelMakeReadOnly();
     }
 
     /**
-     * Resumes all pending watch / push operations.
+     * Resumes all pending watch / push / makeReadOnly operations.
      */
     public void resumeNfcOperations() {
         mOperationsSuspended = false;
@@ -317,6 +362,27 @@
     }
 
     /**
+     * Holds information about pending make read-only operation.
+     */
+    private static class PendingMakeReadOnlyOperation {
+        private final MakeReadOnly_Response mMakeReadOnlyResponseCallback;
+
+        public PendingMakeReadOnlyOperation(MakeReadOnly_Response callback) {
+            mMakeReadOnlyResponseCallback = callback;
+        }
+
+        /**
+         * Completes pending make read-only operation.
+         *
+         * @param error should be null when operation is completed successfully, otherwise,
+         * error object with corresponding NdefErrorType should be provided.
+         */
+        public void complete(NdefError error) {
+            if (mMakeReadOnlyResponseCallback != null) mMakeReadOnlyResponseCallback.call(error);
+        }
+    }
+
+    /**
      * Helper method that creates NdefError object from NdefErrorType.
      */
     private NdefError createError(int errorType, String errorMessage) {
@@ -377,14 +443,17 @@
     }
 
     /**
-     * Enables reader mode, allowing NFC device to read / write NFC tags.
+     * Enables reader mode, allowing NFC device to read / write / make read-only NFC tags.
      * @see android.nfc.NfcAdapter#enableReaderMode
      */
     private void enableReaderModeIfNeeded() {
         if (mReaderCallbackHandler != null || mActivity == null || mNfcAdapter == null) return;
 
-        // Do not enable reader mode, if there are no active push / watch operations.
-        if (mPendingPushOperation == null && mWatchIds.size() == 0) return;
+        // Do not enable reader mode, if there are no active push / makeReadOnly / watch operations.
+        if (mPendingPushOperation == null && mPendingMakeReadOnlyOperation == null
+                && mWatchIds.size() == 0) {
+            return;
+        }
 
         mReaderCallbackHandler = new ReaderCallbackHandler(this);
         mNfcAdapter.enableReaderMode(mActivity, mReaderCallbackHandler,
@@ -410,11 +479,12 @@
     }
 
     /**
-     * Checks if there are pending push / watch operations and disables readre mode
+     * Checks if there are pending push / makeReadOnly / watch operations and disables reader mode
      * whenever necessary.
      */
     private void disableReaderModeIfNeeded() {
-        if (mPendingPushOperation == null && mWatchIds.size() == 0) {
+        if (mPendingPushOperation == null && mPendingMakeReadOnlyOperation == null
+                && mWatchIds.size() == 0) {
             disableReaderMode();
         }
     }
@@ -480,6 +550,58 @@
     }
 
     /**
+     * Handles completion of pending make read-only operation, completes make read-only operation.
+     * On error, invalidates #mTagHandler.
+     */
+    private void pendingMakeReadOnlyOperationCompleted(NdefError error) {
+        completePendingMakeReadOnlyOperation(error);
+        if (error != null) mTagHandler = null;
+    }
+
+    /**
+     * Completes pending make read-only operation and disables reader mode if needed.
+     */
+    private void completePendingMakeReadOnlyOperation(NdefError error) {
+        if (mPendingMakeReadOnlyOperation == null) return;
+
+        mPendingMakeReadOnlyOperation.complete(error);
+        mPendingMakeReadOnlyOperation = null;
+        disableReaderModeIfNeeded();
+    }
+
+    /**
+     * Checks whether there is a #mPendingMakeReadOnlyOperation and make NFC tag read-only. In case
+     * of exception calls pendingMakeReadOnlyOperationCompleted() with appropriate error object.
+     */
+    private void processPendingMakeReadOnlyOperation() {
+        if (mTagHandler == null || mPendingMakeReadOnlyOperation == null) return;
+
+        if (mTagHandler.isTagOutOfRange()) {
+            mTagHandler = null;
+            return;
+        }
+
+        try {
+            mTagHandler.connect();
+            if (mTagHandler.makeReadOnly()) {
+                pendingMakeReadOnlyOperationCompleted(null);
+            } else {
+                Log.w(TAG, "Cannot make NFC tag read-only. The tag cannot be made read-only");
+                pendingMakeReadOnlyOperationCompleted(createError(NdefErrorType.NOT_SUPPORTED,
+                        "Failed to make read-only because the tag cannot be made read-only"));
+            }
+        } catch (TagLostException e) {
+            Log.w(TAG, "Cannot make NFC tag read-only. Tag is lost: " + e.getMessage());
+            pendingMakeReadOnlyOperationCompleted(createError(NdefErrorType.IO_ERROR,
+                    "Failed to make read-only because the tag is lost: " + e.getMessage()));
+        } catch (IOException e) {
+            Log.w(TAG, "Cannot make NFC tag read-only: " + e.getMessage());
+            pendingMakeReadOnlyOperationCompleted(createError(NdefErrorType.IO_ERROR,
+                    "Failed to make read-only due to an IO error: " + e.getMessage()));
+        }
+    }
+
+    /**
      * Reads NdefMessage from a tag and forwards message to matching method.
      */
     private void processPendingWatchOperations() {
@@ -564,11 +686,14 @@
                     createError(NdefErrorType.NOT_SUPPORTED, "This tag is not supported."));
             pendingPushOperationCompleted(
                     createError(NdefErrorType.NOT_SUPPORTED, "This tag is not supported."));
+            pendingMakeReadOnlyOperationCompleted(
+                    createError(NdefErrorType.NOT_SUPPORTED, "This tag is not supported."));
             return;
         }
 
         processPendingWatchOperations();
         processPendingPushOperation();
+        processPendingMakeReadOnlyOperation();
         if (mTagHandler != null && mTagHandler.isConnected()) {
             try {
                 mTagHandler.close();
diff --git a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcTagHandler.java b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcTagHandler.java
index 41b839c6..f1fe244 100644
--- a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcTagHandler.java
+++ b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcTagHandler.java
@@ -56,6 +56,7 @@
     private interface TagTechnologyHandler {
         public void write(NdefMessage message)
                 throws IOException, TagLostException, FormatException, IllegalStateException;
+        public boolean makeReadOnly() throws IOException, TagLostException;
         public NdefMessage read()
                 throws IOException, TagLostException, FormatException, IllegalStateException;
         public boolean canAlwaysOverwrite()
@@ -80,6 +81,11 @@
         }
 
         @Override
+        public boolean makeReadOnly() throws IOException, TagLostException {
+            return mNdef.makeReadOnly();
+        }
+
+        @Override
         public NdefMessage read()
                 throws IOException, TagLostException, FormatException, IllegalStateException {
             return mNdef.getNdefMessage();
@@ -111,6 +117,16 @@
         }
 
         @Override
+        public boolean makeReadOnly() throws IOException, TagLostException {
+            try {
+                mNdefFormattable.formatReadOnly(NdefMessageUtils.emptyNdefMessage());
+            } catch (FormatException e) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
         public NdefMessage read() throws FormatException {
             return NdefMessageUtils.emptyNdefMessage();
         }
@@ -182,6 +198,13 @@
         mTechHandler.write(message);
     }
 
+    /**
+     * Make NFC tag read-only.
+     */
+    public boolean makeReadOnly() throws IOException, TagLostException {
+        return mTechHandler.makeReadOnly();
+    }
+
     public NdefMessage read()
             throws IOException, TagLostException, FormatException, IllegalStateException {
         return mTechHandler.read();
diff --git a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
index e506a6dd..dd3ec5e 100644
--- a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
+++ b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
@@ -28,6 +28,7 @@
 import android.nfc.NfcAdapter.ReaderCallback;
 import android.nfc.NfcManager;
 import android.nfc.Tag;
+import android.nfc.TagLostException;
 import android.nfc.tech.TagTechnology;
 import android.os.Build;
 import android.os.Bundle;
@@ -51,6 +52,7 @@
 import org.chromium.device.mojom.NdefRecord;
 import org.chromium.device.mojom.NdefRecordTypeCategory;
 import org.chromium.device.mojom.NdefWriteOptions;
+import org.chromium.device.mojom.Nfc.MakeReadOnly_Response;
 import org.chromium.device.mojom.Nfc.Push_Response;
 import org.chromium.device.mojom.Nfc.Watch_Response;
 import org.chromium.device.mojom.NfcClient;
@@ -157,6 +159,7 @@
         try {
             doNothing().when(mNfcTagHandler).connect();
             doNothing().when(mNfcTagHandler).write(any(android.nfc.NdefMessage.class));
+            doReturn(true).when(mNfcTagHandler).makeReadOnly();
             doReturn(createNdefMessageWithRecordId(DUMMY_RECORD_ID)).when(mNfcTagHandler).read();
             doNothing().when(mNfcTagHandler).close();
         } catch (IOException | FormatException e) {
@@ -1226,6 +1229,74 @@
     }
 
     /**
+     * Test that Nfc.makeReadOnly() fails if NFC operations are already suspended.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testMakeReadOnlyWhenOperationsAreSuspended() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        nfc.suspendNfcOperations();
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockCallback);
+
+        // Check that makeReadOnly request was cancelled with OPERATION_CANCELLED.
+        verify(mockCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
+     * Test that Nfc.suspendNfcOperations() cancels pending makeReadOnly operation.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testSuspendNfcOperationsCancelMakeReadOnly() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockMakeReadOnlyCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockMakeReadOnlyCallback);
+        nfc.suspendNfcOperations();
+
+        // Check that makeReadOnly request was cancelled with OPERATION_CANCELLED.
+        verify(mockMakeReadOnlyCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
+     * Test that Nfc.makeReadOnly() successful when NFC tag is connected.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testMakeReadOnly() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockCallback);
+        nfc.processPendingOperationsForTesting(mNfcTagHandler);
+        verify(mockCallback).call(mErrorCaptor.capture());
+        assertNull(mErrorCaptor.getValue());
+    }
+
+    /**
+     * Test that Nfc.cancelMakeReadOnly() cancels pending makeReadOnly operation.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testCancelMakeReadOnly() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockMakeReadOnlyCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockMakeReadOnlyCallback);
+        nfc.cancelMakeReadOnly();
+
+        // Check that MakeReadOnly request was cancelled with OPERATION_CANCELLED.
+        verify(mockMakeReadOnlyCallback).call(mErrorCaptor.capture());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
      * Test that Nfc.watch() works correctly and client is notified.
      */
     @Test
@@ -1309,7 +1380,8 @@
 
     /**
      * Test that when the tag in proximity is found to be not NDEF compatible, an error event will
-     * be dispatched to the client and the pending push operation will also be ended with an error.
+     * be dispatched to the client and the pending push and makeReadOnly operations will also be
+     * ended with an error.
      */
     @Test
     @Feature({"NFCTest"})
@@ -1321,8 +1393,11 @@
         Watch_Response mockWatchCallback = mock(Watch_Response.class);
         nfc.watch(mNextWatchId, mockWatchCallback);
         // Start a push.
-        Push_Response mockCallback = mock(Push_Response.class);
-        nfc.push(createMojoNdefMessage(), createNdefWriteOptions(), mockCallback);
+        Push_Response mockPushCallback = mock(Push_Response.class);
+        nfc.push(createMojoNdefMessage(), createNdefWriteOptions(), mockPushCallback);
+        // Start a makeReadOnly.
+        MakeReadOnly_Response mockMakeReadOnlyCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockMakeReadOnlyCallback);
 
         // Pass null tag handler to simulate that the tag is not NDEF compatible.
         nfc.processPendingOperationsForTesting(null);
@@ -1337,14 +1412,20 @@
                         any(NdefMessage.class));
 
         // The pending push failed with the correct error.
-        verify(mockCallback).call(mErrorCaptor.capture());
+        verify(mockPushCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.NOT_SUPPORTED, mErrorCaptor.getValue().errorType);
+
+        // The pending makeReadOnly failed with the correct error.
+        verify(mockMakeReadOnlyCallback).call(mErrorCaptor.capture());
         assertNotNull(mErrorCaptor.getValue());
         assertEquals(NdefErrorType.NOT_SUPPORTED, mErrorCaptor.getValue().errorType);
     }
 
     /**
      * Test that when the tag in proximity is found to be blocked, an error event will
-     * be dispatched to the client and the pending push operation will also be ended with an error.
+     * be dispatched to the client and the pending push and makeReadOnly operations will also be
+     * ended with an error.
      */
     @Test
     @Feature({"NFCTest"})
@@ -1356,8 +1437,11 @@
         Watch_Response mockWatchCallback = mock(Watch_Response.class);
         nfc.watch(mNextWatchId, mockWatchCallback);
         // Start a push.
-        Push_Response mockCallback = mock(Push_Response.class);
-        nfc.push(createMojoNdefMessage(), createNdefWriteOptions(), mockCallback);
+        Push_Response mockPushCallback = mock(Push_Response.class);
+        nfc.push(createMojoNdefMessage(), createNdefWriteOptions(), mockPushCallback);
+        // Start a makeReadOnly.
+        MakeReadOnly_Response mockMakeReadOnlyCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockMakeReadOnlyCallback);
 
         // Mocks blocked 'NFC tag found' event.
         NfcBlocklist.getInstance().setIsTagBlockedForTesting(true);
@@ -1376,7 +1460,12 @@
                         any(NdefMessage.class));
 
         // The pending push failed with the correct error.
-        verify(mockCallback).call(mErrorCaptor.capture());
+        verify(mockPushCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.NOT_SUPPORTED, mErrorCaptor.getValue().errorType);
+
+        // The pending makeReadOnly failed with the correct error.
+        verify(mockMakeReadOnlyCallback).call(mErrorCaptor.capture());
         assertNotNull(mErrorCaptor.getValue());
         assertEquals(NdefErrorType.NOT_SUPPORTED, mErrorCaptor.getValue().errorType);
     }
@@ -1453,6 +1542,28 @@
     }
 
     /**
+     * Test that when tag is disconnected during makeReadOnly operation, TagLostException is
+     * handled.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testTagDisconnectedDuringMakeReadOnly() throws IOException, FormatException {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockCallback = mock(MakeReadOnly_Response.class);
+
+        // Force makeReadOnly operation to fail
+        doThrow(TagLostException.class).when(mNfcTagHandler).makeReadOnly();
+        nfc.makeReadOnly(mockCallback);
+        nfc.processPendingOperationsForTesting(mNfcTagHandler);
+        verify(mockCallback).call(mErrorCaptor.capture());
+
+        // Test that correct error is returned.
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.IO_ERROR, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
      * Test that multiple Nfc.push() invocations do not disable reader mode.
      */
     @Test
@@ -1477,6 +1588,30 @@
     }
 
     /**
+     * Test that multiple Nfc.makeReadOnly() invocations do not disable reader mode.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testMakeReadOnlyMultipleInvocations() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+
+        MakeReadOnly_Response mockCallback1 = mock(MakeReadOnly_Response.class);
+        MakeReadOnly_Response mockCallback2 = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockCallback1);
+        nfc.makeReadOnly(mockCallback2);
+
+        verify(mNfcAdapter, times(1))
+                .enableReaderMode(any(Activity.class), any(ReaderCallback.class), anyInt(),
+                        (Bundle) isNull());
+        verify(mNfcAdapter, times(0)).disableReaderMode(mActivity);
+
+        verify(mockCallback1).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
      * Test that reader mode is disabled and push operation is cancelled with correct error code.
      */
     @Test
@@ -1504,6 +1639,34 @@
     }
 
     /**
+     * Test that reader mode is disabled and makeReadOnly operation is cancelled with correct error
+     * code.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testMakeReadOnlyInvocationWithCancel() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        MakeReadOnly_Response mockCallback = mock(MakeReadOnly_Response.class);
+
+        nfc.makeReadOnly(mockCallback);
+
+        verify(mNfcAdapter, times(1))
+                .enableReaderMode(any(Activity.class), any(ReaderCallback.class), anyInt(),
+                        (Bundle) isNull());
+
+        nfc.cancelMakeReadOnly();
+
+        // Reader mode is disabled.
+        verify(mNfcAdapter, times(1)).disableReaderMode(mActivity);
+
+        // Test that correct error is returned.
+        verify(mockCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
      * Test that reader mode is disabled and two push operations are cancelled with correct
      * error code.
      */
@@ -1540,8 +1703,44 @@
     }
 
     /**
+     * Test that reader mode is disabled and two makeReadOnly operations are cancelled with correct
+     * error code.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testTwoMakeReadOnlyInvocationsWithCancel() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+
+        MakeReadOnly_Response mockCallback1 = mock(MakeReadOnly_Response.class);
+        MakeReadOnly_Response mockCallback2 = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockCallback1);
+        nfc.makeReadOnly(mockCallback2);
+
+        verify(mNfcAdapter, times(1))
+                .enableReaderMode(any(Activity.class), any(ReaderCallback.class), anyInt(),
+                        (Bundle) isNull());
+
+        // The second makeReadOnly should cancel the first makeReadOnly.
+        verify(mockCallback1).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+
+        // Cancel the second makeReadOnly.
+        nfc.cancelMakeReadOnly();
+
+        // Reader mode is disabled after cancelMakeReadOnly is invoked.
+        verify(mNfcAdapter, times(1)).disableReaderMode(mActivity);
+
+        // Test that correct error is returned.
+        verify(mockCallback2).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+    }
+
+    /**
      * Test that reader mode is not disabled when there is an active watch operation and push
-     * operation operation is cancelled.
+     * operation is cancelled.
      */
     @Test
     @Feature({"NFCTest"})
@@ -1574,6 +1773,40 @@
     }
 
     /**
+     * Test that reader mode is not disabled when there is an active watch operation and
+     * makeReadOnly operation is cancelled.
+     */
+    @Test
+    @Feature({"NFCTest"})
+    public void testCancelledMakeReadOnlyDontDisableReaderMode() {
+        TestNfcImpl nfc = new TestNfcImpl(mContext, mDelegate);
+        mDelegate.invokeCallback();
+        Watch_Response mockWatchCallback = mock(Watch_Response.class);
+        nfc.watch(mNextWatchId, mockWatchCallback);
+
+        MakeReadOnly_Response mockMakeReadOnlyCallback = mock(MakeReadOnly_Response.class);
+        nfc.makeReadOnly(mockMakeReadOnlyCallback);
+
+        verify(mNfcAdapter, times(1))
+                .enableReaderMode(any(Activity.class), any(ReaderCallback.class), anyInt(),
+                        (Bundle) isNull());
+
+        nfc.cancelMakeReadOnly();
+
+        // MakeReadOnly was cancelled with OPERATION_CANCELLED.
+        verify(mockMakeReadOnlyCallback).call(mErrorCaptor.capture());
+        assertNotNull(mErrorCaptor.getValue());
+        assertEquals(NdefErrorType.OPERATION_CANCELLED, mErrorCaptor.getValue().errorType);
+
+        verify(mNfcAdapter, times(0)).disableReaderMode(mActivity);
+
+        nfc.cancelWatch(mNextWatchId);
+
+        // Reader mode is disabled when there are no pending makeReadOnly / watch operations.
+        verify(mNfcAdapter, times(1)).disableReaderMode(mActivity);
+    }
+
+    /**
      * Test that Nfc.push() succeeds for NFC messages with EMPTY records.
      */
     @Test
diff --git a/services/device/public/cpp/test/scoped_nfc_overrider.cc b/services/device/public/cpp/test/scoped_nfc_overrider.cc
index 2a408fb..74327b46 100644
--- a/services/device/public/cpp/test/scoped_nfc_overrider.cc
+++ b/services/device/public/cpp/test/scoped_nfc_overrider.cc
@@ -32,6 +32,10 @@
     std::move(callback).Run(nullptr);
   }
   void CancelPush() override {}
+  void MakeReadOnly(MakeReadOnlyCallback callback) override {
+    std::move(callback).Run(nullptr);
+  }
+  void CancelMakeReadOnly() override {}
   void Watch(uint32_t id, WatchCallback callback) override {
     std::move(callback).Run(nullptr);
   }
diff --git a/services/device/public/mojom/fingerprint.mojom b/services/device/public/mojom/fingerprint.mojom
index 699d6610..cab35a1 100644
--- a/services/device/public/mojom/fingerprint.mojom
+++ b/services/device/public/mojom/fingerprint.mojom
@@ -5,7 +5,8 @@
 module device.mojom;
 
 // Note: this needs to stay in sync with
-// src/platform2/system_api/dbus/biod/constants.proto in the ChromeOS repro.
+// src/platform2/system_api/dbus/biod/constants.proto in the ChromeOS repo.
+// Please modify tools/metrics/histograms/enums.xml when changing this.
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused (See FingerprintScanResult metrics
 // enum).
@@ -17,6 +18,7 @@
   TOO_SLOW = 4,
   TOO_FAST = 5,
   IMMOBILE = 6,
+  NO_MATCH = 7,
 };
 
 // Note: this needs to stay in sync with
diff --git a/services/device/public/mojom/nfc.mojom b/services/device/public/mojom/nfc.mojom
index 059b27c..d80f8d3 100644
--- a/services/device/public/mojom/nfc.mojom
+++ b/services/device/public/mojom/nfc.mojom
@@ -101,8 +101,8 @@
   bool overwrite;
 };
 
-// Interface used by Web NFC to push data to NFC devices and get notified of
-// nearby NFC devices.
+// Interface used by Web NFC to push data to NFC devices, make an NFC tag
+// read-only, and get notified of nearby NFC devices.
 interface NFC {
   // NFCClient interface is used to notify |client| when watching for nearby
   // NFC devices.
@@ -115,6 +115,12 @@
   // Cancels pending push request.
   CancelPush();
 
+  // Make an NFC tag permanently read-only.
+  MakeReadOnly() => (NDEFError? error);
+
+  // Cancels pending make read-only request.
+  CancelMakeReadOnly();
+
   // Starts watching for nearby NFC devices. |id| identifies each watch request
   // on the current Mojo connection.
   Watch(uint32 id) => (NDEFError? error);
diff --git a/services/device/serial/bluetooth_serial_device_enumerator.cc b/services/device/serial/bluetooth_serial_device_enumerator.cc
index bd69301..481bb4d0 100644
--- a/services/device/serial/bluetooth_serial_device_enumerator.cc
+++ b/services/device/serial/bluetooth_serial_device_enumerator.cc
@@ -120,7 +120,11 @@
 void BluetoothSerialDeviceEnumerator::PortAdded(
     const std::string& device_address) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!base::Contains(device_ports_, device_address));
+  // Ignore duplicate devices. Some ports send device notifications after
+  // requesting the classic adapter resulting in double detection of devices.
+  if (base::Contains(device_ports_, device_address))
+    return;
+
   auto port = mojom::SerialPortInfo::New();
   port->token = base::UnguessableToken::Create();
   port->path = base::FilePath::FromUTF8Unsafe(device_address);
diff --git a/services/device/serial/bluetooth_serial_port_impl.cc b/services/device/serial/bluetooth_serial_port_impl.cc
index a633d08..330716a 100644
--- a/services/device/serial/bluetooth_serial_port_impl.cc
+++ b/services/device/serial/bluetooth_serial_port_impl.cc
@@ -69,8 +69,7 @@
     return;
   }
 
-  BluetoothDevice::UUIDSet device_uuids = device->GetUUIDs();
-  if (base::Contains(device_uuids, GetSerialPortProfileUUID())) {
+  if (base::Contains(device->GetUUIDs(), GetSerialPortProfileUUID())) {
     auto split_callback = base::SplitOnceCallback(std::move(callback));
     device->ConnectToService(
         GetSerialPortProfileUUID(),
diff --git a/services/viz/public/cpp/gpu/gpu.cc b/services/viz/public/cpp/gpu/gpu.cc
index 16841da4..78462d540 100644
--- a/services/viz/public/cpp/gpu/gpu.cc
+++ b/services/viz/public/cpp/gpu/gpu.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/threading/thread_checker.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -333,6 +334,7 @@
   if (channel)
     return channel;
 
+  SCOPED_UMA_HISTOGRAM_TIMER("GPU.EstablishGpuChannelSyncTime");
   SendEstablishGpuChannelRequest();
   base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL,
                             base::WaitableEvent::InitialState::SIGNALED);
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 5f9a5d9..3835d6af 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -3828,7 +3828,8 @@
   },
   "mac-chrome": {
     "additional_compile_targets": [
-      "chrome"
+      "chrome",
+      "chrome/installer/mac"
     ],
     "isolated_scripts": [
       {
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 57234aa2..52b0957 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -567,6 +567,10 @@
     "label": "//chrome/installer/linux:linux",
     "type": "additional_compile_target",
   },
+  "chrome/installer/mac": {
+    "label": "//chrome/installer/mac:mac",
+    "type": "additional_compile_target",
+  },
   "variations_smoke_tests": {
     "label": "//chrome/test:variations_smoke_tests",
     "type": "generated_script",
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index d204d39..574cebb3 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -277,6 +277,7 @@
       'mac-chrome': {
         'additional_compile_targets': [
           'chrome',
+          'chrome/installer/mac',
         ],
         'mixins': [
           'chrome-swarming-pool',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 67d95e7f..0e2c75d1 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4789,6 +4789,21 @@
             ]
         }
     ],
+    "LightweightReactions": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "LightweightReactions"
+                    ]
+                }
+            ]
+        }
+    ],
     "LiteVideo": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/choosers/date_time_chooser.mojom b/third_party/blink/public/mojom/choosers/date_time_chooser.mojom
index 80765dcb..d3da876 100644
--- a/third_party/blink/public/mojom/choosers/date_time_chooser.mojom
+++ b/third_party/blink/public/mojom/choosers/date_time_chooser.mojom
@@ -33,4 +33,7 @@
   // returned to replace a date/time input field.
   OpenDateTimeDialog(DateTimeDialogValue value) =>
       (bool success, double dialog_value);
+
+  // Dismiss the date/time dialog.
+  CloseDateTimeDialog();
 };
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index edcef21..dfe6954 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -647,6 +647,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_record_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_scan_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_scan_options.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_make_read_only_options.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_make_read_only_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_write_options.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_ndef_write_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_notification_action.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 3100a2b..7314c3f 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -455,6 +455,7 @@
           "//third_party/blink/renderer/modules/nfc/ndef_reading_event_init.idl",
           "//third_party/blink/renderer/modules/nfc/ndef_record.idl",
           "//third_party/blink/renderer/modules/nfc/ndef_record_init.idl",
+          "//third_party/blink/renderer/modules/nfc/ndef_make_read_only_options.idl",
           "//third_party/blink/renderer/modules/nfc/ndef_scan_options.idl",
           "//third_party/blink/renderer/modules/nfc/ndef_write_options.idl",
           "//third_party/blink/renderer/modules/notifications/get_notification_options.idl",
diff --git a/third_party/blink/renderer/core/dom/mutation_observer.cc b/third_party/blink/renderer/core/dom/mutation_observer.cc
index fbbbfd52..2158e80 100644
--- a/third_party/blink/renderer/core/dom/mutation_observer.cc
+++ b/third_party/blink/renderer/core/dom/mutation_observer.cc
@@ -266,8 +266,8 @@
   DCHECK(IsMainThread());
   records_.push_back(mutation);
   ActivateObserver(this);
-  probe::AsyncTaskScheduled(delegate_->GetExecutionContext(), mutation->type(),
-                            mutation->async_task_id());
+  mutation->async_task_context()->Schedule(delegate_->GetExecutionContext(),
+                                           mutation->type());
 }
 
 void MutationObserver::SetHasTransientRegistration() {
@@ -290,8 +290,7 @@
 
 void MutationObserver::CancelInspectorAsyncTasks() {
   for (auto& record : records_) {
-    probe::AsyncTaskCanceled(delegate_->GetExecutionContext(),
-                             record->async_task_id());
+    record->async_task_context()->Cancel();
   }
 }
 
@@ -318,7 +317,7 @@
 
   // Report the first (earliest) stack as the async cause.
   probe::AsyncTask async_task(delegate_->GetExecutionContext(),
-                              records.front()->async_task_id());
+                              records.front()->async_task_context());
   delegate_->Deliver(records, *this);
 }
 
diff --git a/third_party/blink/renderer/core/dom/mutation_record.h b/third_party/blink/renderer/core/dom/mutation_record.h
index 246e76b..1e9c54066 100644
--- a/third_party/blink/renderer/core/dom/mutation_record.h
+++ b/third_party/blink/renderer/core/dom/mutation_record.h
@@ -33,7 +33,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/dom/static_node_list.h"
-#include "third_party/blink/renderer/core/probe/async_task_id.h"
+#include "third_party/blink/renderer/core/probe/async_task_context.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -78,10 +78,10 @@
 
   virtual String oldValue() { return String(); }
 
-  probe::AsyncTaskId* async_task_id() { return &async_task_id_; }
+  probe::AsyncTaskContext* async_task_context() { return &async_task_context_; }
 
  private:
-  probe::AsyncTaskId async_task_id_;
+  probe::AsyncTaskContext async_task_context_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc b/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc
index 857d490..ce3b52bd 100644
--- a/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc
+++ b/third_party/blink/renderer/core/html/forms/external_date_time_chooser.cc
@@ -145,6 +145,10 @@
 
 void ExternalDateTimeChooser::EndChooser() {
   DCHECK(client_);
+  if (date_time_chooser_.is_bound()) {
+    date_time_chooser_->CloseDateTimeDialog();
+    date_time_chooser_.reset();
+  }
   DateTimeChooserClient* client = client_;
   client_ = nullptr;
   client->DidEndChooser();
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 1edff79..de47a88 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1028,7 +1028,19 @@
     return true;
 
   const Element* parent_element = DynamicTo<Element>(parent);
-  return parent_element ? parent_element->IsInCanvasSubtree() : false;
+  if (!parent_element)
+    return false;
+
+  // Authors can include elements as "Fallback content" inside a <canvas> in
+  // order to provide an alternative means to interact with the canvas using
+  // a screen reader. Those should always be included.
+  if (parent_element->IsInCanvasSubtree())
+    return true;
+
+  // LayoutObject::CreateObject() will not create an object for elements
+  // with display:contents. If we do not include a <slot> for that reason,
+  // any descendants will be not be included in the accessibility tree.
+  return parent_element->HasDisplayContentsStyle();
 }
 
 // static
diff --git a/third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.cc b/third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.cc
index 41338b06..a408780 100644
--- a/third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.cc
+++ b/third_party/blink/renderer/modules/bluetooth/bluetooth_uuid.cc
@@ -328,7 +328,7 @@
   // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getcharacteristic
   // https://webbluetoothcg.github.io/web-bluetooth/#dom-bluetoothuuid-getdescriptor
 
-  // If name is an unsigned long, return BluetoothUUID.cannonicalUUI(name) and
+  // If name is an unsigned long, return BluetoothUUID.canonicalUUID(name) and
   // abort this steps.
   if (name->IsUnsignedLong())
     return BluetoothUUID::canonicalUUID(name->GetAsUnsignedLong());
@@ -340,7 +340,7 @@
     return name_str;
 
   // If name is in the corresponding attribute map return
-  // BluetoothUUID.cannonicalUUID(alias).
+  // BluetoothUUID.canonicalUUID(alias).
   NameToAssignedNumberMap* map = nullptr;
   const char* attribute_type = nullptr;
   switch (attribute) {
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.cc b/third_party/blink/renderer/modules/cache_storage/cache.cc
index 47cfa4a..30dccc0 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache.cc
@@ -4,12 +4,12 @@
 
 #include "third_party/blink/renderer/modules/cache_storage/cache.h"
 
+#include <algorithm>
 #include <memory>
 #include <utility>
 
 #include "base/metrics/histogram_macros.h"
 #include "services/network/public/mojom/fetch_api.mojom-blink.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
@@ -32,9 +32,7 @@
 #include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
 #include "third_party/blink/renderer/core/fetch/request.h"
 #include "third_party/blink/renderer/core/fetch/response.h"
-#include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
-#include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
@@ -48,7 +46,6 @@
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
-#include "third_party/blink/renderer/platform/loader/fetch/data_pipe_bytes_consumer.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/network/http_parsers.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.h b/third_party/blink/renderer/modules/cache_storage/cache.h
index 6bee344..630c837 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_H_
 
+#include "base/memory/scoped_refptr.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
index 8f1cc96..9d1da09 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
@@ -6,7 +6,7 @@
 
 #include <utility>
 
-#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
@@ -21,7 +21,6 @@
 #include "third_party/blink/renderer/core/fetch/response.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h"
@@ -29,6 +28,7 @@
 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage.h b/third_party/blink/renderer/modules/cache_storage/cache_storage.h
index 2b1a00d0..2c18113 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage.h
@@ -10,19 +10,17 @@
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_typedefs.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
 #include "third_party/blink/renderer/core/fetch/global_fetch.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache.h"
-#include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
-#include "third_party/blink/renderer/platform/wtf/hash_map.h"
 
 namespace blink {
 
 class CacheStorageBlobClientList;
 class MultiCacheQueryOptions;
+class ScriptState;
 
 class CacheStorage final : public ScriptWrappable,
                            public ActiveScriptWrappable<CacheStorage>,
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc
index 905ceec..9072214b 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc
@@ -4,7 +4,10 @@
 
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
 
+#include <utility>
+
 #include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
 
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h
index 24b6c19..2e810b04 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h
@@ -7,7 +7,6 @@
 
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom-blink.h"
-#include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -15,6 +14,8 @@
 
 namespace blink {
 
+class ExecutionContext;
+
 // This class holds a list of BlobReaderClient implementations alive until
 // they complete or the entire list is garbage collected.
 class MODULES_EXPORT CacheStorageBlobClientList
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.cc
index 3f815ef3..f74830aa 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.cc
@@ -4,7 +4,7 @@
 
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h"
 
-#include <memory>
+#include <sstream>
 
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h b/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h
index e2d67e4..84e0ba3 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h
@@ -6,6 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_STORAGE_TRACE_UTILS_H_
 
 #include <memory>
+#include <string>
 
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink-forward.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_test.cc b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
index 6f6130bc..f2db0e9 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_test.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
@@ -7,13 +7,13 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <utility>
 
-#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "services/network/public/mojom/fetch_api.mojom-blink.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_url_response.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_utils.cc b/third_party/blink/renderer/modules/cache_storage/cache_utils.cc
index d852f2b2..263c9c7b 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_utils.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_utils.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/modules/cache_storage/cache_utils.h"
 
+#include <utility>
+
 #include "third_party/blink/renderer/core/fetch/fetch_response_data.h"
 #include "third_party/blink/renderer/core/fetch/response.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
diff --git a/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc b/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
index c19ef18..0173d7f 100644
--- a/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
+++ b/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
@@ -17,10 +17,6 @@
 #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
 #include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/public/platform/web_security_origin.h"
-#include "third_party/blink/public/platform/web_string.h"
-#include "third_party/blink/public/platform/web_url.h"
-#include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/fileapi/file_reader_loader.h"
 #include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h"
diff --git a/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.cc b/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.cc
index 7de0766..a37b37d 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.cc
@@ -69,7 +69,7 @@
       &CredentialManagerProxy::OnConnectionError, WrapWeakPersistent(this)));
 }
 
-mojom::blink::FederatedAuthRequest* CredentialManagerProxy::FedCMGetRequest() {
+mojom::blink::FederatedAuthRequest* CredentialManagerProxy::FedCmGetRequest() {
   BindRemoteForFedCm(fedcm_get_request_);
   return fedcm_get_request_.get();
 }
diff --git a/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.h b/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.h
index 5fa0dc0..69206fbf 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.h
+++ b/third_party/blink/renderer/modules/credentialmanager/credential_manager_proxy.h
@@ -52,7 +52,7 @@
 
   payments::mojom::blink::PaymentCredential* PaymentCredential();
 
-  mojom::blink::FederatedAuthRequest* FedCMGetRequest();
+  mojom::blink::FederatedAuthRequest* FedCmGetRequest();
 
   void Trace(Visitor*) const override;
 
diff --git a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
index b845b8a..17317b5f 100644
--- a/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanager/credentials_container.cc
@@ -1153,7 +1153,7 @@
         DCHECK(options->federated()->hasPreferAutoSignIn());
         bool prefer_auto_sign_in = options->federated()->preferAutoSignIn();
         auto* fedcm_get_request =
-            CredentialManagerProxy::From(script_state)->FedCMGetRequest();
+            CredentialManagerProxy::From(script_state)->FedCmGetRequest();
         fedcm_get_request->RequestIdToken(
             provider_url, client_id, nonce,
             ToRequestMode(options->federated()->mode()), prefer_auto_sign_in,
diff --git a/third_party/blink/renderer/modules/nfc/ndef_make_read_only_options.idl b/third_party/blink/renderer/modules/nfc/ndef_make_read_only_options.idl
new file mode 100644
index 0000000..12bc355c
--- /dev/null
+++ b/third_party/blink/renderer/modules/nfc/ndef_make_read_only_options.idl
@@ -0,0 +1,8 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://w3c.github.io/web-nfc/#the-ndefmakereadonlyoptions-dictionary
+dictionary NDEFMakeReadOnlyOptions {
+    AbortSignal? signal;
+};
diff --git a/third_party/blink/renderer/modules/nfc/ndef_reader.cc b/third_party/blink/renderer/modules/nfc/ndef_reader.cc
index fa743d8e..250b1873 100644
--- a/third_party/blink/renderer/modules/nfc/ndef_reader.cc
+++ b/third_party/blink/renderer/modules/nfc/ndef_reader.cc
@@ -6,6 +6,7 @@
 
 #include "services/device/public/mojom/nfc.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_ndef_make_read_only_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ndef_scan_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ndef_write_options.h"
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
@@ -59,6 +60,11 @@
   return nullptr;
 }
 
+DOMException* NDEFErrorPtrToDOMException(
+    device::mojom::blink::NDEFErrorPtr error) {
+  return NDEFErrorTypeToDOMException(error->error_type, error->error_message);
+}
+
 constexpr char kNotSupportedOrPermissionDenied[] =
     "Web NFC is unavailable or permission denied.";
 
@@ -68,6 +74,9 @@
 constexpr char kScanAbortMessage[] = "The NFC scan operation was cancelled.";
 
 constexpr char kWriteAbortMessage[] = "The NFC write operation was cancelled.";
+
+constexpr char kMakeReadOnlyAbortMessage[] =
+    "The NFC make read-only operation was cancelled.";
 }  // namespace
 
 // static
@@ -148,15 +157,17 @@
     return;
   }
 
-  if (options->hasSignal()) {
-    if (options->signal()->aborted()) {
+  scan_signal_ = options->getSignalOr(nullptr);
+  if (scan_signal_) {
+    if (scan_signal_->aborted()) {
       scan_resolver_->Reject(MakeGarbageCollected<DOMException>(
           DOMExceptionCode::kAbortError, kScanAbortMessage));
       scan_resolver_.Clear();
       return;
     }
-    options->signal()->AddAlgorithm(
-        WTF::Bind(&NDEFReader::ReadAbort, WrapPersistent(this)));
+    scan_signal_->AddAlgorithm(
+        WTF::Bind(&NDEFReader::ReadAbort, WrapWeakPersistent(this),
+                  WrapWeakPersistent(scan_signal_.Get())));
   }
 
   GetNfcProxy()->StartReading(
@@ -170,8 +181,7 @@
     return;
 
   if (error) {
-    scan_resolver_->Reject(
-        NDEFErrorTypeToDOMException(error->error_type, error->error_message));
+    scan_resolver_->Reject(NDEFErrorPtrToDOMException(std::move(error)));
   } else {
     scan_resolver_->Resolve();
   }
@@ -201,7 +211,12 @@
   GetNfcProxy()->StopReading(this);
 }
 
-void NDEFReader::ReadAbort() {
+void NDEFReader::ReadAbort(AbortSignal* signal) {
+  // There is no RemoveAlgorithm() method on AbortSignal so compare the signal
+  // bound to this callback to the one last passed to scan().
+  if (scan_signal_ != signal)
+    return;
+
   if (scan_resolver_) {
     scan_resolver_->Reject(MakeGarbageCollected<DOMException>(
         DOMExceptionCode::kAbortError, kScanAbortMessage));
@@ -272,14 +287,16 @@
     return;
   }
 
-  if (options->hasSignal()) {
-    if (options->signal()->aborted()) {
+  write_signal_ = options->getSignalOr(nullptr);
+  if (write_signal_) {
+    if (write_signal_->aborted()) {
       resolver->Reject(MakeGarbageCollected<DOMException>(
           DOMExceptionCode::kAbortError, kWriteAbortMessage));
       return;
     }
-    options->signal()->AddAlgorithm(
-        WTF::Bind(&NDEFReader::WriteAbort, WrapPersistent(this)));
+    write_signal_->AddAlgorithm(
+        WTF::Bind(&NDEFReader::WriteAbort, WrapWeakPersistent(this),
+                  WrapWeakPersistent(write_signal_.Get())));
   }
 
   auto callback = WTF::Bind(&NDEFReader::WriteOnRequestCompleted,
@@ -299,17 +316,101 @@
   if (error.is_null()) {
     resolver->Resolve();
   } else {
-    resolver->Reject(
-        NDEFErrorTypeToDOMException(error->error_type, error->error_message));
+    resolver->Reject(NDEFErrorPtrToDOMException(std::move(error)));
   }
 }
 
-void NDEFReader::WriteAbort() {
+void NDEFReader::WriteAbort(AbortSignal* signal) {
+  // There is no RemoveAlgorithm() method on AbortSignal so compare the signal
+  // bound to this callback to the one last passed to write().
+  if (write_signal_ != signal)
+    return;
+
   // WriteOnRequestCompleted() should always be called whether the push
   // operation is cancelled successfully or not.
   GetNfcProxy()->CancelPush();
 }
 
+ScriptPromise NDEFReader::makeReadOnly(ScriptState* script_state,
+                                       const NDEFMakeReadOnlyOptions* options,
+                                       ExceptionState& exception_state) {
+  // https://w3c.github.io/web-nfc/#security-policies
+  // WebNFC API must be only accessible from top level browsing context.
+  if (!DomWindow() || !DomWindow()->GetFrame()->IsMainFrame()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      kChildFrameErrorMessage);
+    return ScriptPromise();
+  }
+
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  make_read_only_requests_.insert(resolver);
+
+  // Add the writer to proxy's writer list for Mojo connection error
+  // notification.
+  GetNfcProxy()->AddWriter(this);
+
+  GetPermissionService()->RequestPermission(
+      CreatePermissionDescriptor(PermissionName::NFC),
+      LocalFrame::HasTransientUserActivation(DomWindow()->GetFrame()),
+      WTF::Bind(&NDEFReader::MakeReadOnlyOnRequestPermission,
+                WrapPersistent(this), WrapPersistent(resolver),
+                WrapPersistent(options)));
+
+  return resolver->Promise();
+}
+
+void NDEFReader::MakeReadOnlyOnRequestPermission(
+    ScriptPromiseResolver* resolver,
+    const NDEFMakeReadOnlyOptions* options,
+    PermissionStatus status) {
+  if (status != PermissionStatus::GRANTED) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kNotAllowedError, "NFC permission request denied."));
+    return;
+  }
+
+  make_read_only_signal_ = options->getSignalOr(nullptr);
+  if (make_read_only_signal_) {
+    if (make_read_only_signal_->aborted()) {
+      resolver->Reject(MakeGarbageCollected<DOMException>(
+          DOMExceptionCode::kAbortError, kMakeReadOnlyAbortMessage));
+      return;
+    }
+    make_read_only_signal_->AddAlgorithm(
+        WTF::Bind(&NDEFReader::MakeReadOnlyAbort, WrapWeakPersistent(this),
+                  WrapWeakPersistent(make_read_only_signal_.Get())));
+  }
+
+  auto callback = WTF::Bind(&NDEFReader::MakeReadOnlyOnRequestCompleted,
+                            WrapPersistent(this), WrapPersistent(resolver));
+  GetNfcProxy()->MakeReadOnly(std::move(callback));
+}
+
+void NDEFReader::MakeReadOnlyOnRequestCompleted(
+    ScriptPromiseResolver* resolver,
+    device::mojom::blink::NDEFErrorPtr error) {
+  DCHECK(make_read_only_requests_.Contains(resolver));
+
+  make_read_only_requests_.erase(resolver);
+
+  if (error.is_null()) {
+    resolver->Resolve();
+  } else {
+    resolver->Reject(NDEFErrorPtrToDOMException(std::move(error)));
+  }
+}
+
+void NDEFReader::MakeReadOnlyAbort(AbortSignal* signal) {
+  // There is no RemoveAlgorithm() method on AbortSignal so compare the signal
+  // bound to this callback to the one last passed to makeReadOnly().
+  if (make_read_only_signal_ != signal)
+    return;
+
+  // MakeReadOnlyOnRequestCompleted() should always be called whether the
+  // makeReadOnly operation is cancelled successfully or not.
+  GetNfcProxy()->CancelMakeReadOnly();
+}
+
 NFCProxy* NDEFReader::GetNfcProxy() const {
   DCHECK(DomWindow());
   return NFCProxy::From(*DomWindow());
@@ -318,7 +419,11 @@
 void NDEFReader::Trace(Visitor* visitor) const {
   visitor->Trace(permission_service_);
   visitor->Trace(scan_resolver_);
+  visitor->Trace(scan_signal_);
   visitor->Trace(write_requests_);
+  visitor->Trace(write_signal_);
+  visitor->Trace(make_read_only_requests_);
+  visitor->Trace(make_read_only_signal_);
   EventTargetWithInlineData::Trace(visitor);
   ActiveScriptWrappable::Trace(visitor);
   ExecutionContextLifecycleObserver::Trace(visitor);
@@ -348,11 +453,10 @@
   // If the mojo connection breaks, All push requests will be rejected with a
   // default error.
 
-  // Script may execute during a call to Resolve(). Swap these sets to prevent
+  // Script may execute during a call to Reject(). Swap these sets to prevent
   // concurrent modification.
   HeapHashSet<Member<ScriptPromiseResolver>> write_requests;
   write_requests_.swap(write_requests);
-  write_requests_.clear();
   for (ScriptPromiseResolver* resolver : write_requests) {
     resolver->Reject(NDEFErrorTypeToDOMException(
         device::mojom::blink::NDEFErrorType::NOT_SUPPORTED,
@@ -360,4 +464,19 @@
   }
 }
 
+void NDEFReader::MakeReadOnlyOnMojoConnectionError() {
+  // If the mojo connection breaks, All makeReadOnly requests will be rejected
+  // with a default error.
+
+  // Script may execute during a call to Reject(). Swap these sets to prevent
+  // concurrent modification.
+  HeapHashSet<Member<ScriptPromiseResolver>> make_read_only_requests;
+  make_read_only_requests_.swap(make_read_only_requests);
+  for (ScriptPromiseResolver* resolver : make_read_only_requests) {
+    resolver->Reject(NDEFErrorTypeToDOMException(
+        device::mojom::blink::NDEFErrorType::NOT_SUPPORTED,
+        kNotSupportedOrPermissionDenied));
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/ndef_reader.h b/third_party/blink/renderer/modules/nfc/ndef_reader.h
index e31634f..be8ae5e 100644
--- a/third_party/blink/renderer/modules/nfc/ndef_reader.h
+++ b/third_party/blink/renderer/modules/nfc/ndef_reader.h
@@ -19,7 +19,9 @@
 
 namespace blink {
 
+class AbortSignal;
 class NDEFScanOptions;
+class NDEFMakeReadOnlyOptions;
 class NDEFWriteOptions;
 class NFCProxy;
 class ScriptPromiseResolver;
@@ -56,6 +58,11 @@
                       const NDEFWriteOptions* options,
                       ExceptionState& exception_state);
 
+  // Make NFC tag permanently read-only.
+  ScriptPromise makeReadOnly(ScriptState* script_state,
+                             const NDEFMakeReadOnlyOptions* options,
+                             ExceptionState& exception_state);
+
   void Trace(Visitor*) const override;
 
   // Called by NFCProxy for dispatching events.
@@ -66,6 +73,7 @@
   // Called by NFCProxy for notification about connection error.
   void ReadOnMojoConnectionError();
   void WriteOnMojoConnectionError();
+  void MakeReadOnlyOnMojoConnectionError();
 
  private:
   // ExecutionContextLifecycleObserver overrides.
@@ -73,13 +81,17 @@
 
   NFCProxy* GetNfcProxy() const;
 
-  void ReadAbort();
+  void ReadAbort(AbortSignal* signal);
   void ReadOnRequestCompleted(device::mojom::blink::NDEFErrorPtr error);
 
-  void WriteAbort();
+  void WriteAbort(AbortSignal* signal);
   void WriteOnRequestCompleted(ScriptPromiseResolver* resolver,
                                device::mojom::blink::NDEFErrorPtr error);
 
+  void MakeReadOnlyAbort(AbortSignal* signal);
+  void MakeReadOnlyOnRequestCompleted(ScriptPromiseResolver* resolver,
+                                      device::mojom::blink::NDEFErrorPtr error);
+
   // Read Permission handling
   void ReadOnRequestPermission(const NDEFScanOptions* options,
                                mojom::blink::PermissionStatus status);
@@ -91,10 +103,16 @@
       device::mojom::blink::NDEFMessagePtr ndef_message,
       mojom::blink::PermissionStatus status);
 
+  // Make read-only permission handling
+  void MakeReadOnlyOnRequestPermission(ScriptPromiseResolver* resolver,
+                                       const NDEFMakeReadOnlyOptions* options,
+                                       mojom::blink::PermissionStatus status);
+
   // |scan_resolver_| is kept here to handle Mojo connection failures because in
   // that case the callback passed to Watch() won't be called and
   // mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
   Member<ScriptPromiseResolver> scan_resolver_;
+  Member<AbortSignal> scan_signal_;
 
   HeapMojoRemote<mojom::blink::PermissionService> permission_service_;
   mojom::blink::PermissionService* GetPermissionService();
@@ -102,8 +120,14 @@
   // |write_requests_| are kept here to handle Mojo connection failures because
   // in that case the callback passed to Push() won't be called and
   // mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
-  // This list will also be used by AbortSignal.
   HeapHashSet<Member<ScriptPromiseResolver>> write_requests_;
+  Member<AbortSignal> write_signal_;
+
+  // |make_read_only_requests_| are kept here to handle Mojo connection failures
+  // because in that case the callback passed to MakeReadOnly() won't be called
+  // and mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
+  HeapHashSet<Member<ScriptPromiseResolver>> make_read_only_requests_;
+  Member<AbortSignal> make_read_only_signal_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/ndef_reader.idl b/third_party/blink/renderer/modules/nfc/ndef_reader.idl
index 63575c5e..b7d7330c 100644
--- a/third_party/blink/renderer/modules/nfc/ndef_reader.idl
+++ b/third_party/blink/renderer/modules/nfc/ndef_reader.idl
@@ -23,4 +23,7 @@
     [CallWith=ScriptState, RaisesException, MeasureAs=WebNfcNdefWriterWrite] Promise<void> write(
         NDEFMessageSource message,
         optional NDEFWriteOptions options={});
+
+    [CallWith=ScriptState, RaisesException, RuntimeEnabled=WebNFCMakeReadOnly] Promise<void> makeReadOnly(
+        optional NDEFMakeReadOnlyOptions options={});
 };
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy.cc b/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
index aa09e8a..6b8c9ce8 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
@@ -89,6 +89,17 @@
   nfc_remote_->CancelPush();
 }
 
+void NFCProxy::MakeReadOnly(device::mojom::blink::NFC::PushCallback cb) {
+  EnsureMojoConnection();
+  nfc_remote_->MakeReadOnly(std::move(cb));
+}
+
+void NFCProxy::CancelMakeReadOnly() {
+  if (!nfc_remote_)
+    return;
+  nfc_remote_->CancelMakeReadOnly();
+}
+
 // device::mojom::blink::NFCClient implementation.
 void NFCProxy::OnWatch(const Vector<uint32_t>& watch_ids,
                        const String& serial_number,
@@ -184,6 +195,7 @@
   // Notify all writers about the connection error and clear the list.
   for (auto& writer : writers_) {
     writer->WriteOnMojoConnectionError();
+    writer->MakeReadOnlyOnMojoConnectionError();
   }
   writers_.clear();
 }
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy.h b/third_party/blink/renderer/modules/nfc/nfc_proxy.h
index dfa072d..886998bf 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy.h
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy.h
@@ -46,6 +46,8 @@
             device::mojom::blink::NDEFWriteOptionsPtr,
             device::mojom::blink::NFC::PushCallback);
   void CancelPush();
+  void MakeReadOnly(device::mojom::blink::NFC::MakeReadOnlyCallback);
+  void CancelMakeReadOnly();
 
  private:
   // Implementation of device::mojom::blink::NFCClient.
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
index 4451445..0be76ec 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
@@ -107,6 +107,8 @@
     std::move(callback).Run(nullptr);
   }
   void CancelPush() override {}
+  void MakeReadOnly(MakeReadOnlyCallback callback) override {}
+  void CancelMakeReadOnly() override {}
   void Watch(uint32_t id, WatchCallback callback) override {
     if (watch_error_) {
       std::move(callback).Run(watch_error_.Clone());
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index a52f0b8..a9d40c2 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2618,6 +2618,11 @@
       status: {"Android": "stable", "default": "test"},
     },
     {
+      name: "WebNFCMakeReadOnly",
+      depends_on: ["WebNFC"],
+      status: "experimental",
+    },
+    {
       name: "WebOTP",
       status: "stable",
     },
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index e3a0b72..82249b13 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1522,6 +1522,7 @@
 external/wpt/visual-viewport/viewport-scroll-event-manual.html [ Skip ]
 external/wpt/visual-viewport/viewport-url-bar-changes-height-manual.html [ Skip ]
 external/wpt/web-nfc/NDEFReader-read-document-hidden-manual.https.html [ Skip ]
+external/wpt/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html [ Skip ]
 external/wpt/web-nfc/NDEFReader-write-document-hidden-manual.https.html [ Skip ]
 external/wpt/web-nfc/nfc-prompt-manual.https.html [ Skip ]
 external/wpt/screen-wake-lock/wakelock-document-hidden-manual.https.html [ Skip ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index fe34353..39ab0a7 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -7318,10 +7318,6 @@
 crbug.com/1256763 [ Linux ] virtual/gpu-rasterization/images/color-profile-svg-fill-text.html [ Failure Pass ]
 crbug.com/1259277 fast/events/message-port-multi.html [ Skip ]
 
-# Sheriff 2021-10-20
-crbug.com/1261770 crbug.com/1245166 [ Linux ] external/wpt/web-bundle/subresource-loading/script-relative-url-in-web-bundle-cors.https.tentative.sub.html [ Failure Pass ]
-crbug.com/1261770 crbug.com/1245166 [ Linux ] virtual/wbn-from-network/external/wpt/web-bundle/subresource-loading/script-relative-url-in-web-bundle-cors.https.tentative.sub.html [ Failure Pass ]
-
 # Sheriff 2021-10-26
 crbug.com/1263349 [ Linux ] external/wpt/resource-timing/object-not-found-after-cross-origin-redirect.html [ Failure Pass ]
 crbug.com/1249043 external/wpt/html/cross-origin-embedder-policy/reporting-navigation.https.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/resources/chromium/nfc-mock.js b/third_party/blink/web_tests/external/wpt/resources/chromium/nfc-mock.js
index 6a0437a..a7ce560 100644
--- a/third_party/blink/web_tests/external/wpt/resources/chromium/nfc-mock.js
+++ b/third_party/blink/web_tests/external/wpt/resources/chromium/nfc-mock.js
@@ -139,8 +139,10 @@
       this.hw_status_ = NFCHWStatus.ENABLED;
       this.pushed_message_ = null;
       this.pending_write_options_ = null;
-      this.pending_promise_func_ = null;
+      this.pending_push_promise_func_ = null;
       this.push_completed_ = true;
+      this.pending_make_read_only_promise_func_ = null;
+      this.make_read_only_completed_ = true;
       this.client_ = null;
       this.watchers_ = [];
       this.reading_messages_ = [];
@@ -152,11 +154,11 @@
 
     // NFC delegate functions.
     async push(message, options) {
-      let error = this.getHWError();
+      const error = this.getHWError();
       if (error)
         return error;
       // Cancels previous pending push operation.
-      if (this.pending_promise_func_) {
+      if (this.pending_push_promise_func_) {
         this.cancelPendingPushOperation();
       }
 
@@ -165,7 +167,7 @@
       return new Promise(resolve => {
         if (this.operations_suspended_ || !this.push_completed_) {
           // Leaves the push pending.
-          this.pending_promise_func_ = resolve;
+          this.pending_push_promise_func_ = resolve;
         } else if (this.is_formatted_tag_ && !options.overwrite) {
           // Resolves with NotAllowedError if there are NDEF records on the device
           // and overwrite is false.
@@ -184,13 +186,40 @@
       return createNDEFError(null);
     }
 
+    async makeReadOnly(options) {
+      const error = this.getHWError();
+      if (error)
+        return error;
+      // Cancels previous pending makeReadOnly operation.
+      if (this.pending_make_read_only_promise_func_) {
+        this.cancelPendingMakeReadOnlyOperation();
+      }
+
+      if (this.operations_suspended_ || !this.make_read_only_completed_) {
+        // Leaves the makeReadOnly pending.
+        return new Promise(resolve => {
+          this.pending_make_read_only_promise_func_ = resolve;
+        });
+      } else if (this.data_transfer_failed_) {
+        // Resolves with NetworkError if data transfer fails.
+        return createNDEFError(NDEFErrorType.IO_ERROR);
+      } else {
+        return createNDEFError(null);
+      }
+    }
+
+    async cancelMakeReadOnly() {
+      this.cancelPendingMakeReadOnlyOperation();
+      return createNDEFError(null);
+    }
+
     setClient(client) {
       this.client_ = client;
     }
 
     async watch(id) {
       assert_true(id > 0);
-      let error = this.getHWError();
+      const error = this.getHWError();
       if (error) {
         return error;
       }
@@ -245,22 +274,27 @@
       this.push_completed_ = result;
     }
 
+    setPendingMakeReadOnlyCompleted(result) {
+      this.make_read_only_completed_ = result;
+    }
+
     reset() {
       this.hw_status_ = NFCHWStatus.ENABLED;
       this.watchers_ = [];
       this.reading_messages_ = [];
       this.operations_suspended_ = false;
       this.cancelPendingPushOperation();
+      this.cancelPendingMakeReadOnlyOperation();
       this.is_formatted_tag_ = false;
       this.data_transfer_failed_ = false;
       this.should_close_pipe_on_request_ = false;
     }
 
     cancelPendingPushOperation() {
-      if (this.pending_promise_func_) {
-        this.pending_promise_func_(
+      if (this.pending_push_promise_func_) {
+        this.pending_push_promise_func_(
             createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
-        this.pending_promise_func_ = null;
+        this.pending_push_promise_func_ = null;
       }
 
       this.pushed_message_ = null;
@@ -268,6 +302,16 @@
       this.push_completed_ = true;
     }
 
+    cancelPendingMakeReadOnlyOperation() {
+      if (this.pending_make_read_only_promise_func_) {
+        this.pending_make_read_only_promise_func_(
+            createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
+        this.pending_make_read_only_promise_func_ = null;
+      }
+
+      this.make_read_only_completed_ = true;
+    }
+
     // Sets message that is used to deliver NFC reading updates.
     setReadingMessage(message) {
       this.reading_messages_.push(message);
@@ -303,9 +347,15 @@
         }
       }
       // Resumes pending push operation.
-      if (this.pending_promise_func_ && this.push_completed_) {
-        this.pending_promise_func_(createNDEFError(null));
-        this.pending_promise_func_ = null;
+      if (this.pending_push_promise_func_ && this.push_completed_) {
+        this.pending_push_promise_func_(createNDEFError(null));
+        this.pending_push_promise_func_ = null;
+      }
+      // Resumes pending makeReadOnly operation.
+      if (this.pending_make_read_only_promise_func_ &&
+          this.make_read_only_completed_) {
+        this.pending_make_read_only_promise_func_(createNDEFError(null));
+        this.pending_make_read_only_promise_func_ = null;
       }
     }
 
@@ -319,10 +369,16 @@
         });
       }
       // Reject the pending push with NotSupportedError.
-      if (this.pending_promise_func_) {
-        this.pending_promise_func_(
+      if (this.pending_push_promise_func_) {
+        this.pending_push_promise_func_(
             createNDEFError(NDEFErrorType.NOT_SUPPORTED));
-        this.pending_promise_func_ = null;
+        this.pending_push_promise_func_ = null;
+      }
+      // Reject the pending makeReadOnly with NotSupportedError.
+      if (this.pending_make_read_only_promise_func_) {
+        this.pending_make_read_only_promise_func_(
+            createNDEFError(NDEFErrorType.NOT_SUPPORTED));
+        this.pending_make_read_only_promise_func_ = null;
       }
     }
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html
new file mode 100644
index 0000000..25cefcb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<title>NDEFReader.makeReadOnly respect page visibility changes</title>
+<link rel="help" href="https://w3c.github.io/web-nfc/#visible-document">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+promise_test(async t => {
+  const ndef = new NDEFReader();
+  const p1 = ndef.makeReadOnly();
+
+  return await new Promise((resolve, reject) => {
+    p1.then(() => {
+      assert_false(document.hidden);
+      resolve();
+    }).catch(e => {
+      reject();
+    });
+  });
+}, "Test NDEFReader.makeReadOnly operation should be suspended when document is not visible");
+
+</script>
+
+<p>Step1: switch the page to the background, then tap an NFC tag.</p>
+<p>Step2: switch back to the page, then tap the tag again.</p>
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_make-read-only.https.window.js b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_make-read-only.https.window.js
new file mode 100644
index 0000000..244dd63
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_make-read-only.https.window.js
@@ -0,0 +1,176 @@
+// META: script=/resources/testharness.js
+// META: script=/resources/testharnessreport.js
+// META: script=resources/nfc-helpers.js
+
+// NDEFReader.makeReadOnly method
+// https://w3c.github.io/web-nfc/#dom-ndefreader-makereadonly
+
+'use strict';
+
+const invalid_signals = ['string', 123, {}, true, Symbol(), () => {}, self];
+
+nfc_test(async t => {
+  await test_driver.set_permission({name: 'nfc'}, 'denied', false);
+  const ndef = new NDEFReader();
+  await promise_rejects_dom(t, 'NotAllowedError', ndef.makeReadOnly());
+}, 'NDEFReader.makeReadOnly should fail if user permission is not granted.');
+
+// We do not provide NFC mock here to simulate that there has no available
+// implementation for NFC Mojo interface.
+nfc_test(async (t, mockNFC) => {
+  mockNFC.simulateClosedPipe();
+  const ndef = new NDEFReader();
+  await promise_rejects_dom(t, 'NotSupportedError', ndef.makeReadOnly());
+}, 'NDEFReader.makeReadOnly should fail if no implementation for NFC Mojo interface is available.');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef = new NDEFReader();
+  const controller = new AbortController();
+
+  // Make sure makeReadOnly is pending
+  mockNFC.setPendingMakeReadOnlyCompleted(false);
+  const p = ndef.makeReadOnly({signal: controller.signal});
+  const rejected = promise_rejects_dom(t, 'AbortError', p);
+  let callback_called = false;
+  await new Promise(resolve => {
+    t.step_timeout(() => {
+      callback_called = true;
+      controller.abort();
+      resolve();
+    }, 10);
+  });
+  await rejected;
+  assert_true(callback_called, 'timeout should have caused the abort');
+}, 'NDEFReader.makeReadOnly should fail if request is aborted before makeReadOnly happends.');
+
+nfc_test(async t => {
+  const ndef = new NDEFReader();
+  const controller = new AbortController();
+  assert_false(controller.signal.aborted);
+  controller.abort();
+  assert_true(controller.signal.aborted);
+  await promise_rejects_dom(
+      t, 'AbortError', ndef.makeReadOnly({signal: controller.signal}));
+}, 'NDEFReader.makeReadOnly should fail if signal is already aborted.');
+
+nfc_test(async t => {
+  const ndef = new NDEFReader();
+  const promises = [];
+  invalid_signals.forEach(invalid_signal => {
+    promises.push(promise_rejects_js(
+        t, TypeError, ndef.makeReadOnly({signal: invalid_signal})));
+  });
+  await Promise.all(promises);
+}, 'NDEFReader.write should fail if signal is not an AbortSignal.');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef1 = new NDEFReader();
+  const ndef2 = new NDEFReader();
+  const controller = new AbortController();
+  const p1 = ndef1.makeReadOnly({signal: controller.signal});
+
+  // Even though makeReadOnly request is grantable,
+  // this abort should be processed synchronously.
+  controller.abort();
+  await promise_rejects_dom(t, 'AbortError', p1);
+
+  await ndef2.makeReadOnly();
+}, 'Synchronously signaled abort.');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef = new NDEFReader();
+  mockNFC.setHWStatus(NFCHWStatus.DISABLED);
+  await promise_rejects_dom(t, 'NotReadableError', ndef.makeReadOnly());
+}, 'NDEFReader.makeReadOnly should fail when NFC HW is disabled.');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef = new NDEFReader();
+  mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED);
+  await promise_rejects_dom(t, 'NotSupportedError', ndef.makeReadOnly());
+}, 'NDEFReader.makeReadOnly should fail when NFC HW is not supported.');
+
+nfc_test(async () => {
+  await new Promise((resolve, reject) => {
+    const iframe = document.createElement('iframe');
+    iframe.srcdoc = `<script>
+                      window.onmessage = async (message) => {
+                        if (message.data === "Ready") {
+                          try {
+                            const ndef = new NDEFReader();
+                            await ndef.makeReadOnly();
+                            parent.postMessage("Failure", "*");
+                          } catch (error) {
+                            if (error.name == "InvalidStateError") {
+                              parent.postMessage("Success", "*");
+                            } else {
+                              parent.postMessage("Failure", "*");
+                            }
+                          }
+                        }
+                      };
+                    </script>`;
+    iframe.onload = () => iframe.contentWindow.postMessage('Ready', '*');
+    document.body.appendChild(iframe);
+    window.onmessage = message => {
+      if (message.data == 'Success') {
+        resolve();
+      } else if (message.data == 'Failure') {
+        reject();
+      }
+    }
+  });
+}, 'Test that WebNFC API is not accessible from iframe context.');
+
+nfc_test(async () => {
+  const ndef = new NDEFReader();
+  await ndef.makeReadOnly();
+}, 'NDEFReader.makeReadOnly should succeed when NFC HW is enabled');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef1 = new NDEFReader();
+  const ndef2 = new NDEFReader();
+
+  // Make sure the first makeReadOnly will be pending.
+  mockNFC.setPendingMakeReadOnlyCompleted(false);
+
+  const p1 = ndef1.makeReadOnly();
+  const p2 = ndef2.makeReadOnly();
+
+  await promise_rejects_dom(t, 'AbortError', p1);
+  await p2;
+}, 'NDEFReader.makeReadOnly should replace all previously configured makeReadOnly operations.');
+
+nfc_test(async () => {
+  const ndef = new NDEFReader();
+
+  const controller1 = new AbortController();
+  await ndef.makeReadOnly({signal: controller1.signal});
+
+  const controller2 = new AbortController();
+  const promise = ndef.makeReadOnly({signal: controller2.signal});
+  controller1.abort();
+  await promise;
+}, 'NDEFReader.makeReadOnly signals are independent.');
+
+nfc_test(async (t, mockNFC) => {
+  // Make sure the makeReadOnly will be pending in the mock.
+  mockNFC.setPendingMakeReadOnlyCompleted(false);
+
+  const ndef1 = new NDEFReader();
+  const promise = ndef1.makeReadOnly();
+
+  // Just to make sure the makeReadOnly() request has already reached to the
+  // mock.
+  const ndef2 = new NDEFReader();
+  await ndef2.scan();
+
+  mockNFC.simulateNonNDEFTagDiscovered();
+  await promise_rejects_dom(t, 'NotSupportedError', promise);
+}, 'NDEFReader.makeReadOnly should fail when the NFC device coming up does not expose \
+NDEF technology.');
+
+nfc_test(async (t, mockNFC) => {
+  const ndef = new NDEFReader();
+  mockNFC.simulateDataTransferFails();
+  await promise_rejects_dom(t, 'NetworkError', ndef.makeReadOnly());
+}, 'NDEFReader.makeReadOnly should fail with NetworkError when NFC data transfer fails.');
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_scan.https.html b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_scan.https.html
index 281f3db..cb35aba2 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_scan.https.html
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_scan.https.html
@@ -123,6 +123,18 @@
 }, "Test that NDEFReader.scan rejects if NDEFScanOptions.signal aborts right after \
 the scan invocation.");
 
+nfc_test(async () => {
+  const ndef = new NDEFReader();
+
+  const controller1 = new AbortController();
+  await ndef.scan({signal: controller1.signal});
+
+  controller1.abort();
+
+  const controller2 = new AbortController();
+  await ndef.scan({signal: controller2.signal});
+}, "Test that NDEFReader.scan signals are independant.");
+
 nfc_test(async (t, mockNFC) => {
   const ndef = new NDEFReader();
   const controller = new AbortController();
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_write.https.html b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_write.https.html
index 35e6dd372..8f6cd81 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_write.https.html
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NDEFReader_write.https.html
@@ -411,6 +411,18 @@
   });
 }, "NDEFReader.write should replace all previously configured write operations.");
 
+nfc_test(async () => {
+  const ndef = new NDEFReader();
+
+  const controller1 = new AbortController();
+  await ndef.write(test_text_data, {signal: controller1.signal});
+
+  const controller2 = new AbortController();
+  const promise = ndef.write(test_text_data, {signal: controller2.signal});
+  controller1.abort();
+  await promise;
+}, 'NDEFReader.write signals are independent.');
+
 nfc_test(async (t, mockNFC) => {
   const ndef = new NDEFReader();
   await ndef.write({ records: [{ recordType: "mime", data: test_buffer_data }] });
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/README.md b/third_party/blink/web_tests/external/wpt/web-nfc/README.md
index 15c6534..b51018d 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/README.md
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/README.md
@@ -14,6 +14,7 @@
     setHWStatus(number status); // Sets the hardware status.
     setReadingMessage(NDEFMessageInit message); // Sets message that is used to deliver NFC reading updates.
     setPendingPushCompleted(boolean result); // Sets if the pending push is completed.
+    setPendingMakeReadOnlyCompleted(boolean result); // Sets if the pending makeReadOnly is completed.
     pushedMessage(); // Gets the pushed `NDEFMessageSource`.
     writeOptions(); // Gets the pushed `NDEFWriteOptions`.
     simulateNonNDEFTagDiscovered(); // Simulates that the NFC device discovered does not expose NDEF technology.
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index 177658f..03b5a03 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -5724,6 +5724,7 @@
     getter onreading
     getter onreadingerror
     method constructor
+    method makeReadOnly
     method scan
     method write
     setter onreading
diff --git a/tools/accessibility/inspect/README.md b/tools/accessibility/inspect/README.md
index bdfd710..3674d59 100644
--- a/tools/accessibility/inspect/README.md
+++ b/tools/accessibility/inspect/README.md
@@ -96,6 +96,19 @@
 result tree. Also see example-tree-filters.txt in tools/accessibility/inspect
 for more examples.
 
+### API option for Windows
+
+On windows, we support two accessibility APIS, IAccessible2 and UI-AUTOMATION.
+By default, IA2 is selected.
+
+To dump a tree with IAccessible2:
+
+`--api=ia2`
+
+To dump a tree with UI-AUTOMATION:
+
+`--api=uia`
+
 ### Other options
 
 `--help` for help
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 2b7c3bd7..f3a2092 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -499,6 +499,10 @@
     "includes": [3220],
     "structures": [3240],
   },
+  "<(SHARED_INTERMEDIATE_DIR)/ash/webui/system_extensions_internals_ui/ash_system_extensions_internals_resources.grd": {
+    "META": {"sizes": {"includes": [10],}},
+    "includes": [3260],
+  },
   "chromeos/resources/chromeos_resources.grd": {
     "includes": [3280],
   },
diff --git a/tools/mb/PRESUBMIT.py b/tools/mb/PRESUBMIT.py
index 76ae524..6d378b30 100644
--- a/tools/mb/PRESUBMIT.py
+++ b/tools/mb/PRESUBMIT.py
@@ -53,7 +53,7 @@
       version='2.7',
       # pylint complains about Checkfreeze not being defined, its probably
       # finding a different PRESUBMIT.py
-      files_to_skip='PRESUBMIT_test.py',
+      files_to_skip=['PRESUBMIT_test.py'],
       # Disabling certain python3-specific warnings until the conversion
       # is complete.
       disabled_warnings=[
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 8e35c791..abbb6e3 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -26960,7 +26960,6 @@
   <int value="338" label="NTPContentSuggestionsEnabled"/>
   <int value="339" label="WebRtcUdpPortRange"/>
   <int value="340" label="EnableSha1ForLocalAnchors"/>
-  <int value="341" label="WebRestrictionsAuthority"/>
   <int value="342" label="ComponentUpdatesEnabled"/>
   <int value="343" label="ExternalStorageReadOnly"/>
   <int value="344" label="RemoteAccessHostAllowUiAccessForRemoteAssistance"/>
@@ -31438,6 +31437,25 @@
   <int value="1212" label="Pairing Failed: Error UI Settings Button Pressed"/>
 </enum>
 
+<enum name="FastPairPairingMethod">
+  <int value="0" label="Fast Pair Pairing"/>
+  <int value="1" label="System UI Pairing"/>
+</enum>
+
+<enum name="FastPairRetroactiveEngagementFlowEvent">
+  <int value="1" label="Associate Account UI Shown"/>
+  <int value="11" label="Associate Account Dismissed By User"/>
+  <int value="12" label="Associate Account Dismissed By OS"/>
+  <int value="13" label="Associate Account: Learn More Button Pressed"/>
+  <int value="14" label="Associate Account: Save Button Pressed"/>
+  <int value="131"
+      label="Associate Account: Save Button Pressed After Learn More Pressed"/>
+  <int value="132"
+      label="Associate Account Dismissed By User After Learn More Pressed"/>
+  <int value="133"
+      label="Associate Account Dismissed By OS After Learn More Pressed"/>
+</enum>
+
 <enum name="FaultTolerantHeap">
   <int value="0" label="FTH_OFF">FTH is completely off.</int>
   <int value="1" label="FTH_HKLM">FTH is enabled in HKLM.</int>
@@ -37663,6 +37681,7 @@
   <int value="4" label="Too Slow"/>
   <int value="5" label="Too Fast"/>
   <int value="6" label="Immobile"/>
+  <int value="7" label="No Match"/>
 </enum>
 
 <enum name="FingerprintSensorMode">
@@ -45870,6 +45889,17 @@
       label="App state was UIApplicationStateBackground during XTE."/>
 </enum>
 
+<enum name="IOSColdStartType">
+  <int value="0" label="Cold start"/>
+  <int value="1" label="Cold start with FRE"/>
+  <int value="2" label="Cold start after device restore"/>
+  <int value="3" label="Cold start after Chrome upgrade"/>
+  <int value="4" label="Cold start after device restore and Chrome upgrade"/>
+  <int value="5" label="Cold start with unknown device restore"/>
+  <int value="6"
+      label="Cold start with unknown device restore and Chrome upgrade"/>
+</enum>
+
 <enum name="IOSContentSizeCategory">
   <int value="0" label="Unspecified"/>
   <int value="1" label="ExtraSmall"/>
@@ -45925,6 +45955,13 @@
   <int value="3" label="All Tabs"/>
 </enum>
 
+<enum name="IOSDeviceRestoreSignedinState">
+  <int value="0" label="User not signed in before iOS device restore"/>
+  <int value="1"
+      label="User signed in before iOS device restore, and signed out after"/>
+  <int value="2" label="User signed in before and after iOS device restore"/>
+</enum>
+
 <enum name="IOSDeviceThermalState">
   <int value="0" label="0-Unknown"/>
   <int value="1" label="1-Nominal"/>
@@ -46527,6 +46564,14 @@
   <int value="11" label="YCbCr with other subsampling"/>
 </enum>
 
+<enum name="JsonParserExtension">
+  <int value="0" label="C style comment (/**/)"/>
+  <int value="1" label="C++ style comment (//)"/>
+  <int value="2" label="UTF-8 \xNN string escape"/>
+  <int value="3" label="Vertical tab string escape (\\v)"/>
+  <int value="4" label="Unescaped ASCII control character [0x00,0x1F]"/>
+</enum>
+
 <enum name="JumplisticonsDeleteCategory">
   <int value="0" label="Success"/>
   <int value="1" label="Fail as directory name length exceeds MAX_PATH"/>
@@ -51377,6 +51422,7 @@
       label="PrefetchNotificationSchedulingIntegration:enabled"/>
   <int value="-738957187" label="OmniboxUIExperimentSwapTitleAndUrl:disabled"/>
   <int value="-737740161" label="SupportForAddPasswordsInSettings:enabled"/>
+  <int value="-735827773" label="PageInfoHistoryDesktop:enabled"/>
   <int value="-735084806" label="NewTabLoadingAnimation:enabled"/>
   <int value="-734301625"
       label="AutofillImportNonFocusableCreditCardForms:disabled"/>
@@ -54768,6 +54814,7 @@
   <int value="1811266774" label="WebUITabStripNewTabButtonInTabStrip:disabled"/>
   <int value="1811744551" label="InterestFeedV2Hearts:enabled"/>
   <int value="1812368073" label="enable-new-app-list-mixer"/>
+  <int value="1813205714" label="PageInfoHistoryDesktop:disabled"/>
   <int value="1813370929"
       label="MigrateDefaultChromeAppToWebAppsNonGSuite:enabled"/>
   <int value="1813741175" label="ZeroSuggestPrefetching:enabled"/>
@@ -57237,6 +57284,18 @@
   <int value="2" label="Unsafe"/>
 </enum>
 
+<enum name="MediaFoundationRendererErrorReason">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="CdmProxyReceivedInInvalidState"/>
+  <int value="2" label="FailedToSetSourceOnMediaEngine"/>
+  <int value="3" label="kFailedToSetCurrentTime"/>
+  <int value="4" label="kFailedToPlay"/>
+  <int value="5" label="kOnPlaybackError"/>
+  <int value="6" label="kOnDCompSurfaceReceivedError"/>
+  <int value="7" label="kOnDCompSurfaceHandleSetError"/>
+  <int value="8" label="kOnConnectionError"/>
+</enum>
+
 <enum name="MediaGalleriesUsageType">
   <int value="0" label="Gallery added from permission dialog"/>
   <int value="1" label="Gallery permission added from permission dialog"/>
@@ -79901,7 +79960,9 @@
     User turned off sync from the Desktop Identity Consistency internals UI.
   </int>
   <int value="9" label="Account removed from device">
-    Signout forced because account was removed from device.
+    Signout forced because account was removed from device. With M98, on iOS,
+    this value is split with &quot;Signed out forced by iOS device
+    restore&quot;.
   </int>
   <int value="10" label="Signout forced on profile load">
     Signout forced when profile is loaded as browser sign-in is no longer
@@ -79922,6 +79983,10 @@
     Signout was forced because account ID migration from email to Gaia ID was
     forced.
   </int>
+  <int value="15" label="Signed out forced by iOS device restore">
+    Sign-out forced because the account was removed from the device after a
+    device restore. iOS only.
+  </int>
 </enum>
 
 <enum name="SigninSource">
@@ -82636,7 +82701,11 @@
              resource"/>
   <int value="6"
       label="Abandoned because no content was visible at the beginning of
-             startup"/>
+             startup. Hint: often caused by showing the profile picker
+             instead of immediately opening a browser window on startup"/>
+  <int value="7"
+      label="Abandoned because the content was already painted before
+             profiling started"/>
 </enum>
 
 <enum name="StartupTabPreloaderLoadDecisionCause">
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 390de08d..87f77d0 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -2449,7 +2449,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.CacheSize" units="MB"
-    expires_after="2022-04-24">
+    expires_after="2022-06-26">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -2458,7 +2458,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.CodeSize" units="MB"
-    expires_after="2021-12-26">
+    expires_after="2022-06-26">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
@@ -2469,7 +2469,7 @@
 </histogram>
 
 <histogram name="Android.PackageStats.DataSize" units="MB"
-    expires_after="2021-12-26">
+    expires_after="2022-06-26">
   <owner>nyquist@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index db6ef73..ef6e33c 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -207,6 +207,35 @@
   </token>
 </histogram>
 
+<histogram name="Bluetooth.ChromeOS.FastPair.PairingMethod"
+    enum="FastPairPairingMethod" expires_after="2022-09-20">
+  <owner>shanefitz@google.com</owner>
+  <owner>julietlevesque@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records the count of eligible users who pair devices without Fast Pair and
+    with Fast Pair. This metric is emitted after a device pairing event to the
+    Bluetooth adapter, once verified that it is not a Fast Pair device pairing,
+    and that the pairing was to connect to the Bluetooth adapter, not to
+    disconnect from it. It is also emitted when the Fast Pair OnDevicePaired
+    event fires.
+  </summary>
+</histogram>
+
+<histogram name="Bluetooth.ChromeOS.FastPair.RetroactiveEngagementFunnel.Steps"
+    enum="FastPairRetroactiveEngagementFlowEvent" expires_after="2022-09-20">
+  <owner>shanefitz@google.com</owner>
+  <owner>julietlevesque@google.com</owner>
+  <owner>chromeos-cross-device-eng@google.com</owner>
+  <summary>
+    Records each step in the Fast Pair flow for the retroactive pairing
+    scenario. Emitted when the associate account UI is shown, the associate
+    account UI is dismissed, the save to account button is pressed on the
+    associate account UI, and when the learn more button is pressed on the
+    associate account UI.
+  </summary>
+</histogram>
+
 <histogram
     name="Bluetooth.ChromeOS.FastPair.TotalUxPairTime.{FastPairPairingProtocol}"
     units="ms" expires_after="2022-09-20">
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 457c719..1a6643fc 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -991,6 +991,17 @@
   <summary>The error states generated by OpenGL calls.</summary>
 </histogram>
 
+<histogram name="GPU.EstablishGpuChannelSyncTime" units="ms"
+    expires_after="2022-05-13">
+  <owner>cduvall@chromium.org</owner>
+  <owner>jam@chromium.org</owner>
+  <summary>
+    Measures the time it takes to synchronously establish the GPU channel.
+    Logged every time a sync call to EstableGpuChannelSync is made and a channel
+    is not already available.
+  </summary>
+</histogram>
+
 <histogram name="GPU.FenceSupport" enum="BooleanAvailable" expires_after="M77">
   <owner>reveman@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index ab728a9..5278b94b 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -3184,12 +3184,22 @@
   </summary>
 </histogram>
 
+<histogram name="Media.MediaFoundationRenderer.ErrorReason"
+    enum="MediaFoundationRendererErrorReason" expires_after="2022-08-30">
+  <owner>xhwang@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    Recorded when the MediaFoundationRenderer or MediaFoundationRendererClient
+    hits an error.
+  </summary>
+</histogram>
+
 <histogram name="Media.MediaFoundationRenderer.PlaybackError" enum="Hresult"
     expires_after="2022-08-30">
   <owner>xhwang@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
-    Recorded when the MediaFoundationRenderer his a playback error. Some errors
+    Recorded when the MediaFoundationRenderer hits a playback error. Some errors
     are part of the normal user flow (e.g. sleep/resume) and are not a bug.
   </summary>
 </histogram>
diff --git a/tools/metrics/histograms/metadata/profile/histograms.xml b/tools/metrics/histograms/metadata/profile/histograms.xml
index c928b173..518d54d 100644
--- a/tools/metrics/histograms/metadata/profile/histograms.xml
+++ b/tools/metrics/histograms/metadata/profile/histograms.xml
@@ -885,6 +885,37 @@
   </summary>
 </histogram>
 
+<histogram name="ProfilePicker.FirstProfileTime.FirstWebContentsFinishReason"
+    enum="StartupProfilingFinishReason" expires_after="2022-04-03">
+  <owner>dgn@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    [Desktop] The reason for which profile picker startup profiling was deemed
+    complete. Logged once per session when the user opens a profile from the
+    profile picker shown on startup.
+
+    Used to understand user behavior shifts when
+    ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint regresses
+  </summary>
+</histogram>
+
+<histogram name="ProfilePicker.FirstProfileTime.FirstWebContentsNonEmptyPaint"
+    units="ms" expires_after="2022-04-03">
+  <owner>dgn@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Measure the elapsed time from when the user selects a profile on the startup
+    profile picker to the first non empty paint of the first web contents. This
+    is recorded when the user selects an existing profile from that profile
+    picker shown on startup.
+
+    Together with ProfilePicker.StartupTime.FirstPaint.FromApplicationStart,
+    this metric is intended to capture the startup latency for a common case
+    where Startup.FirstWebContents.NonEmptyPaint3 is not recorded due to the
+    profile picker interrupting browser startup.
+  </summary>
+</histogram>
+
 <histogram name="ProfilePicker.NewProfileCreateShortcut" enum="BooleanCreated"
     expires_after="M90">
   <owner>msalama@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/security/histograms.xml b/tools/metrics/histograms/metadata/security/histograms.xml
index a0e4c22..022b7e1 100644
--- a/tools/metrics/histograms/metadata/security/histograms.xml
+++ b/tools/metrics/histograms/metadata/security/histograms.xml
@@ -195,6 +195,17 @@
   </summary>
 </histogram>
 
+<histogram name="Security.JSONParser.ChromiumExtensionUsage"
+    enum="JsonParserExtension" expires_after="2022-04-01">
+  <owner>rsesek@chromium.org</owner>
+  <owner>chrome-platform-security@google.com</owner>
+  <summary>
+    Records the usage of the base::JSONParser's non-RFC-8259-conforming
+    extensions when parsing JSON documents. This histogram is emitted each time
+    an instance of non-conforming syntax is detected in a document.
+  </summary>
+</histogram>
+
 <histogram name="Security.LegacyTLS.DownloadStarted" enum="Boolean"
     expires_after="2021-08-09">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index 2018a1b..ee60088 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -744,6 +744,28 @@
   </summary>
 </histogram>
 
+<histogram name="Signin.IOSDeviceRestoreIdentityCountAfter" units="identities"
+    expires_after="2022-04-01">
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Records the number of identities after iOS device restore. Recorded at cold
+    start when a device restore is detected, and only if the user was signed in
+    before the device backup.
+  </summary>
+</histogram>
+
+<histogram name="Signin.IOSDeviceRestoreIdentityCountBefore" units="identities"
+    expires_after="2022-04-01">
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Records the number of identities before iOS device restore. Recorded at cold
+    start when a device restore is detected, and only if the user was signed in
+    before the device backup.
+  </summary>
+</histogram>
+
 <histogram name="Signin.IOSDeviceRestoreSentinelError"
     enum="SigninIOSDeviceRestoreSentinelError" expires_after="2022-04-01">
   <owner>jlebel@chromium.org</owner>
@@ -764,6 +786,16 @@
   </summary>
 </histogram>
 
+<histogram name="Signin.IOSDeviceRestoreSignedInState"
+    enum="IOSDeviceRestoreSignedinState" expires_after="2022-04-01">
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@google.com</owner>
+  <summary>
+    Records whether the user is signed in or signed out before and after a
+    device restore. Recorded at cold start when a device restore is detected.
+  </summary>
+</histogram>
+
 <histogram name="Signin.IOSGaiaCookiePresentOnNavigation" enum="BooleanPresent"
     expires_after="2022-01-30">
   <obsolete>
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index 89ca5f26..e8399ec37 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -773,6 +773,16 @@
   </summary>
 </histogram>
 
+<histogram name="Startup.IOSColdStartType" enum="IOSColdStartType"
+    expires_after="2022-04-01">
+  <owner>jlebel@chromium.org</owner>
+  <owner>chrome-signin-team@chromium.org</owner>
+  <summary>
+    Records session type for a cold start. It is recorded at cold start.
+    Histogram only for iOS.
+  </summary>
+</histogram>
+
 <histogram name="Startup.LoadTime.ApplicationStartToChromeMain" units="ms"
     expires_after="never">
 <!-- expires-never: used to diagnose regressions to Startup.FirstWebContents.NonEmptyPaint3 -->
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index 68acd4d6..db6f464 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -1199,11 +1199,7 @@
     measure the same time in GetUpdatesResponse-s.
   </summary>
   <token key="SyncModelType" variants="SyncModelType">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
+    <variant name=""/>
   </token>
 </histogram>
 
@@ -1220,11 +1216,7 @@
     data type, but those are *not* counted in this metric.
   </summary>
   <token key="SyncModelType" variants="SyncModelType">
-    <variant name="">
-      <obsolete>
-        Base histogram. Use suffixes of this histogram instead.
-      </obsolete>
-    </variant>
+    <variant name=""/>
   </token>
 </histogram>
 
diff --git a/tools/traffic_annotation/auditor/safe_list.txt b/tools/traffic_annotation/auditor/safe_list.txt
index 496112f..816700b1 100644
--- a/tools/traffic_annotation/auditor/safe_list.txt
+++ b/tools/traffic_annotation/auditor/safe_list.txt
@@ -14,14 +14,19 @@
 test_annotation,net/quic/quic_chromium_client_session_peer.cc
 test_annotation,net/tools/quic/quic_http_proxy_backend_stream.cc
 # TODO(crbug.com/995852): Fix Android-specific annotations.
-missing,chrome/browser/endpoint_fetcher/endpoint_fetcher.cc
 missing,android_webview/browser/network_service/aw_proxy_config_monitor.cc
+missing,chrome/android/features/tab_ui/java/src/or/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestions.java
+missing,chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsServerFetcher.java
+missing,chrome/android/java/src/org/chromium/chrome/browser/download/OMADownloadHandler.java
+missing,chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
+missing,chrome/android/java/src/org/chromium/chrome/browser/net/connectivitydetector/ConnectivityDetector.java
+missing,chrome/android/java/src/org/chromium/chrome/browser/offlinepages/measurements/OfflineMeasurementsBackgroundTask.java
+missing,chrome/android/java/src/org/chromium/chrome/browser/omaha/OmahaBase.java
 missing,chrome/browser/android/compositor/scene_layer/contextual_search_scene_layer.cc
 missing,chrome/browser/android/feedback/connectivity_checker.cc
 missing,chrome/browser/android/webapk/webapk_installer.cc
-missing,net/proxy_resolution/proxy_config_service_android.cc
-missing,chrome/android/features/tab_ui/java/src/or/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestions.java
-missing,chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabSuggestionsServerFetcher.java
 missing,chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsServiceProxy.java
+missing,chrome/browser/endpoint_fetcher/endpoint_fetcher.cc
 missing,chrome/browser/page_annotations/android/java/src/org/chromium/chrome/browser/page_annotations/PageAnnotationsServiceProxy.java
 missing,chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/StorePersistedTabData.java
+missing,net/proxy_resolution/proxy_config_service_android.cc
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index b86ab54..a1029d24 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -345,4 +345,6 @@
  <item id="gstatic_change_password_scripts" added_in_milestone="98" content_hash_code="04d28714" os_list="android" file_path="components/password_manager/core/browser/password_scripts_fetcher_impl.cc" />
  <item id="chrome_cast_discovery_api" added_in_milestone="98" content_hash_code="0502b792" os_list="linux,windows,chromeos" file_path="chrome/browser/media/router/discovery/access_code/access_code_cast_discovery_interface.cc" />
  <item id="fedcm" added_in_milestone="98" content_hash_code="02f3cbfc" os_list="linux,windows,chromeos,android" file_path="content/browser/webid/idp_network_request_manager.cc" />
+ <item id="minidump_uploader_android" added_in_milestone="98" content_hash_code="0327544e" os_list="android" file_path="components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/util/HttpURLConnectionFactoryImpl.java" />
+ <item id="chrome_variations_android" added_in_milestone="98" content_hash_code="04eeb61f" os_list="android" file_path="components/variations/android/java/src/org/chromium/components/variations/firstrun/VariationsSeedFetcher.java" />
 </annotations>
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index acb92f0..9944f59 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -23,6 +23,7 @@
       <traffic_annotation unique_id="cached_image_fetcher"/>
       <traffic_annotation unique_id="chime_sdk"/>
       <traffic_annotation unique_id="chrome_android_hats"/>
+      <traffic_annotation unique_id="chrome_variations_android"/>
       <traffic_annotation unique_id="contextual_search_resolve"/>
       <traffic_annotation unique_id="customtabs_parallel_request"/>
       <traffic_annotation unique_id="download_bitmap"/>
@@ -34,6 +35,7 @@
       <traffic_annotation unique_id="gstatic_change_password_scripts"/>
       <traffic_annotation unique_id="gstatic_onboarding_definition"/>
       <traffic_annotation unique_id="kids_chrome_management_client_classify_url"/>
+      <traffic_annotation unique_id="minidump_uploader_android"/>
       <traffic_annotation unique_id="partner_bookmarks_reader_get_favicon"/>
       <traffic_annotation unique_id="permission_request_creator"/>
       <traffic_annotation unique_id="publish_note_request"/>
diff --git a/ui/gl/gl_image_egl_angle_vulkan.cc b/ui/gl/gl_image_egl_angle_vulkan.cc
index f26c107..7a43590 100644
--- a/ui/gl/gl_image_egl_angle_vulkan.cc
+++ b/ui/gl/gl_image_egl_angle_vulkan.cc
@@ -11,6 +11,10 @@
 #include "ui/gl/gl_context_egl.h"
 #include "ui/gl/gl_surface_egl.h"
 
+#define EGL_VULKAN_IMAGE_ANGLE 0x34D3
+#define EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE 0x34D4
+#define EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE 0x34D5
+
 namespace gl {
 
 GLImageEGLAngleVulkan::GLImageEGLAngleVulkan(const gfx::Size& size)
@@ -18,53 +22,30 @@
 
 GLImageEGLAngleVulkan::~GLImageEGLAngleVulkan() = default;
 
-bool GLImageEGLAngleVulkan::Initialize(unsigned int texture) {
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
-  if (egl_display == EGL_NO_DISPLAY) {
-    LOG(ERROR) << "Failed to retrieve EGLDisplay";
-    return false;
-  }
+bool GLImageEGLAngleVulkan::Initialize(VkImage image,
+                                       const VkImageCreateInfo* create_info) {
+  DCHECK(image != VK_NULL_HANDLE);
+  DCHECK(create_info);
 
-  GLContext* current_context = GLContext::GetCurrent();
-  if (!current_context || !current_context->IsCurrent(nullptr)) {
-    LOG(ERROR) << "No gl context bound to the current thread";
-    return false;
-  }
+  uint64_t info = reinterpret_cast<uint64_t>(create_info);
+  EGLint attribs[] = {
+      EGL_VULKAN_IMAGE_CREATE_INFO_HI_ANGLE,
+      static_cast<EGLint>((info >> 32) & 0xffffffff),
+      EGL_VULKAN_IMAGE_CREATE_INFO_LO_ANGLE,
+      static_cast<EGLint>(info & 0xffffffff),
+      EGL_NONE,
+  };
 
-  EGLContext context_handle =
-      reinterpret_cast<EGLContext>(current_context->GetHandle());
-  DCHECK_NE(context_handle, EGL_NO_CONTEXT);
   bool result = GLImageEGL::Initialize(
-      context_handle, EGL_GL_TEXTURE_2D_KHR,
-      reinterpret_cast<EGLClientBuffer>(texture), nullptr);
+      EGL_NO_CONTEXT, EGL_VULKAN_IMAGE_ANGLE,
+      reinterpret_cast<EGLClientBuffer>(&image), attribs);
 
   if (!result) {
-    LOG(ERROR) << "Create EGLImage from texture failed";
+    LOG(ERROR) << "Create EGLImage from VkImage failed";
     return false;
   }
 
   return true;
 }
 
-VkImage GLImageEGLAngleVulkan::ExportVkImage(VkImageCreateInfo* info) {
-  DCHECK(info);
-
-  EGLDisplay egl_display = gl::GLSurfaceEGL::GetHardwareDisplay();
-  if (egl_display == EGL_NO_DISPLAY) {
-    LOG(ERROR) << "Failed to retrieve EGLDisplay";
-    return VK_NULL_HANDLE;
-  }
-
-  VkImage vk_image = VK_NULL_HANDLE;
-  if (!eglExportVkImageANGLE(egl_display, egl_image_, &vk_image, info)) {
-    LOG(ERROR) << "Export VkImage from EGLImage failed";
-    return VK_NULL_HANDLE;
-  }
-
-  DCHECK_NE(vk_image, VK_NULL_HANDLE);
-  DCHECK_EQ(info->sType, VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO);
-  DCHECK_EQ(size_, gfx::Size(info->extent.width, info->extent.height));
-  return vk_image;
-}
-
 }  // namespace gl
diff --git a/ui/gl/gl_image_egl_angle_vulkan.h b/ui/gl/gl_image_egl_angle_vulkan.h
index 81dc41c..c14e85e 100644
--- a/ui/gl/gl_image_egl_angle_vulkan.h
+++ b/ui/gl/gl_image_egl_angle_vulkan.h
@@ -21,8 +21,7 @@
   GLImageEGLAngleVulkan(const GLImageEGLAngleVulkan&) = delete;
   GLImageEGLAngleVulkan& operator=(const GLImageEGLAngleVulkan&) = delete;
 
-  bool Initialize(unsigned int texture);
-  VkImage ExportVkImage(VkImageCreateInfo* info);
+  bool Initialize(VkImage image, const VkImageCreateInfo* create_info);
 
  protected:
   ~GLImageEGLAngleVulkan() override;
diff --git a/weblayer/browser/android/javatests/weblayer_instrumentation_test_versions.py b/weblayer/browser/android/javatests/weblayer_instrumentation_test_versions.py
index db3362f..f9ecc34 100755
--- a/weblayer/browser/android/javatests/weblayer_instrumentation_test_versions.py
+++ b/weblayer/browser/android/javatests/weblayer_instrumentation_test_versions.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env vpython
+#!/usr/bin/env vpython3
 #
 # Copyright 2020 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
@@ -113,7 +113,7 @@
             'Only "Skip" is supported in the skew test expectations.')
 
     # Iterate over the first (and only) item since can't index over a frozenset.
-    tag = iter(expectation.tags).next()
+    tag = next(iter(expectation.tags))
     if tag_matches(tag, impl_version, client_version):
       tests.append(expectation.test)
   return tests