diff --git a/DEPS b/DEPS
index f511bc8..edc260c 100644
--- a/DEPS
+++ b/DEPS
@@ -307,11 +307,11 @@
   # 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': '93fbb15abfe4161e4280d20498749b3a1e12415b',
+  'v8_revision': '4b00f2f2737f7c2b1e4ca41ca2dc7c8a55c805d7',
   # 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': 'e939f9a9cf76533d3ddaa1bb279813708e7e7d94',
+  'angle_revision': '0ddebba548bf85ae807d1d7d3641bd79f7a189b2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -394,7 +394,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': 'a89ba04022e79cb59bdcdf510392cd2e6cd749b3',
+  'devtools_frontend_revision': '5eb9e244097ff37b64738ca222787348f6f05920',
   # 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.
@@ -418,7 +418,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': 'a1a25d682c7d3ed4206b4b40e6c36f82bfcfa0ea',
+  'dawn_revision': 'e9f87b69b96384c508aa2d408601e0881035ff3a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -818,7 +818,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '99685ad0653a10fa9254bf04244740661468e99e',
+    '8224cc0dada49684ca5f40d26820ec2d3962ee33',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -980,7 +980,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'nk3APjbujCskiTtdIxbZeBGQneKr1rs-yPkqtu3psY8C',
+          'version': 'gZ9Ou2JmKklDd0fCtaO5p8SJucTure9ee9bL5XMz5S8C',
       },
     ],
     'condition': 'checkout_android',
@@ -1196,7 +1196,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'c6bf12d0f4fd78aa7dbc7e5379b2cb7c2503b58b',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '63790d3a0292fcede670a1a0259e9356916c9bd6',
     'condition': 'checkout_src_internal',
   },
 
@@ -1841,7 +1841,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'f20c5f7b8f53904edaa98651d764e1b8305d7c14',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '3cce50ae4bc4384dd61b7d2dc877203993b09b19',
+    Var('webrtc_git') + '/src.git' + '@' + 'ee2fcbab428934630b39e0be128cb0fcd0573aae',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -1964,7 +1964,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'VV5-j8t5Lb3-eKI0lxEy1uXkoRaNKXXeu1J_p__GpWYC',
+        'version': 'rtkaLDytxJJJ7cYs6fkrOMlmbQMrwNvdS7pM3dZuWdAC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -3944,7 +3944,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        '1bd7f39830947c4615bf1d40810c3adf862d20bc',
+        '5e31ebc3259a7849928fafb8bdeb3cd8ca48c8d8',
       'condition': 'checkout_src_internal',
   },
 
@@ -4004,7 +4004,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'b41b25ff5c18d9589ef7a1e8e2bec24bffc75acc',
+        '34c58e6d6072333decae4e8c0b3b93316920f550',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_feature_list_creator.cc b/android_webview/browser/aw_feature_list_creator.cc
index 6c965909..7653a5bab 100644
--- a/android_webview/browser/aw_feature_list_creator.cc
+++ b/android_webview/browser/aw_feature_list_creator.cc
@@ -243,7 +243,9 @@
   variations::UIStringOverrider ui_string_overrider;
   variations_field_trial_creator_ =
       std::make_unique<variations::VariationsFieldTrialCreator>(
-          client_.get(), std::move(seed_store), ui_string_overrider);
+          client_.get(), std::move(seed_store), ui_string_overrider,
+          // Limited entropy field trials are not supported on WebView.
+          /*limited_entropy_synthetic_trial=*/nullptr);
   variations_field_trial_creator_->OverrideVariationsPlatform(
       variations::Study::PLATFORM_ANDROID_WEBVIEW);
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 8196862..4699bf4 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -4007,6 +4007,7 @@
     "//components/desks_storage",
     "//components/desks_storage:test_support",
     "//components/feature_engagement/public",
+    "//components/feature_engagement/test:test_support",
     "//components/global_media_controls",
     "//components/language/core/browser:browser",
     "//components/live_caption:constants",
diff --git a/ash/api/tasks/fake_tasks_client.cc b/ash/api/tasks/fake_tasks_client.cc
index 99536fb6..7b5787d 100644
--- a/ash/api/tasks/fake_tasks_client.cc
+++ b/ash/api/tasks/fake_tasks_client.cc
@@ -85,31 +85,12 @@
 void FakeTasksClient::AddTask(const std::string& task_list_id,
                               const std::string& title,
                               TasksClient::OnTaskSavedCallback callback) {
-  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
-  CHECK(task_list_iter != tasks_in_task_lists_.end());
-
-  auto pending_task = std::make_unique<Task>(
-      base::Uuid::GenerateRandomV4().AsLowercaseString(), title,
-      /*completed=*/false,
-      /*due=*/absl::nullopt,
-      /*has_subtasks=*/false, /*has_email_link=*/false,
-      /*has_notes=*/false,
-      /*updated=*/base::Time::Now());
-
-  auto pending_callback = base::BindOnce(
-      [](ui::ListModel<Task>* tasks, std::unique_ptr<Task> pending_task,
-         TasksClient::OnTaskSavedCallback callback) {
-        const auto* const task = tasks->AddAt(
-            /*index=*/0, std::move(pending_task));
-        std::move(callback).Run(task);
-      },
-      task_list_iter->second.get(), std::move(pending_task),
-      std::move(callback));
-
   if (paused_) {
-    pending_add_task_callbacks_.push_back(std::move(pending_callback));
+    pending_add_task_callbacks_.push_back(
+        base::BindOnce(&FakeTasksClient::AddTaskImpl, base::Unretained(this),
+                       task_list_id, title, std::move(callback)));
   } else {
-    std::move(pending_callback).Run();
+    AddTaskImpl(task_list_id, title, std::move(callback));
   }
 }
 
@@ -117,26 +98,12 @@
                                  const std::string& task_id,
                                  const std::string& title,
                                  TasksClient::OnTaskSavedCallback callback) {
-  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
-  CHECK(task_list_iter != tasks_in_task_lists_.end());
-
-  const auto task_iter = std::find_if(
-      task_list_iter->second->begin(), task_list_iter->second->end(),
-      [&task_id](const auto& task) { return task->id == task_id; });
-  CHECK(task_iter != task_list_iter->second->end());
-
-  auto pending_callback = base::BindOnce(
-      [](Task* task, const std::string& title,
-         TasksClient::OnTaskSavedCallback callback) {
-        task->title = title;
-        std::move(callback).Run(task);
-      },
-      task_iter->get(), title, std::move(callback));
-
   if (paused_) {
-    pending_update_task_callbacks_.push_back(std::move(pending_callback));
+    pending_update_task_callbacks_.push_back(
+        base::BindOnce(&FakeTasksClient::UpdateTaskImpl, base::Unretained(this),
+                       task_list_id, task_id, title, std::move(callback)));
   } else {
-    std::move(pending_callback).Run();
+    UpdateTaskImpl(task_list_id, task_id, title, std::move(callback));
   }
 }
 
@@ -174,6 +141,52 @@
   return RunPendingCallbacks(pending_update_task_callbacks_);
 }
 
+void FakeTasksClient::AddTaskImpl(const std::string& task_list_id,
+                                  const std::string& title,
+                                  TasksClient::OnTaskSavedCallback callback) {
+  if (run_with_errors_) {
+    std::move(callback).Run(/*task=*/nullptr);
+    return;
+  }
+
+  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
+  CHECK(task_list_iter != tasks_in_task_lists_.end());
+
+  auto pending_task = std::make_unique<Task>(
+      base::Uuid::GenerateRandomV4().AsLowercaseString(), title,
+      /*completed=*/false,
+      /*due=*/absl::nullopt,
+      /*has_subtasks=*/false, /*has_email_link=*/false,
+      /*has_notes=*/false,
+      /*updated=*/base::Time::Now());
+
+  const auto* const task = task_list_iter->second->AddAt(
+      /*index=*/0, std::move(pending_task));
+  std::move(callback).Run(task);
+}
+
+void FakeTasksClient::UpdateTaskImpl(
+    const std::string& task_list_id,
+    const std::string& task_id,
+    const std::string& title,
+    TasksClient::OnTaskSavedCallback callback) {
+  if (run_with_errors_) {
+    std::move(callback).Run(/*task=*/nullptr);
+    return;
+  }
+
+  auto task_list_iter = tasks_in_task_lists_.find(task_list_id);
+  CHECK(task_list_iter != tasks_in_task_lists_.end());
+
+  const auto task_iter = std::find_if(
+      task_list_iter->second->begin(), task_list_iter->second->end(),
+      [&task_id](const auto& task) { return task->id == task_id; });
+  CHECK(task_iter != task_list_iter->second->end());
+
+  task_iter->get()->title = title;
+  std::move(callback).Run(task_iter->get());
+}
+
 void FakeTasksClient::PopulateTasks(base::Time tasks_due_time) {
   task_lists_ = std::make_unique<ui::ListModel<TaskList>>();
 
diff --git a/ash/api/tasks/fake_tasks_client.h b/ash/api/tasks/fake_tasks_client.h
index b74f007..5943f57 100644
--- a/ash/api/tasks/fake_tasks_client.h
+++ b/ash/api/tasks/fake_tasks_client.h
@@ -66,10 +66,21 @@
   size_t RunPendingUpdateTaskCallbacks();
 
   void set_paused(bool paused) { paused_ = paused; }
+  void set_run_with_errors(bool run_with_errors) {
+    run_with_errors_ = run_with_errors;
+  }
 
   ui::ListModel<TaskList>* task_lists() { return task_lists_.get(); }
 
  private:
+  void AddTaskImpl(const std::string& task_list_id,
+                   const std::string& title,
+                   TasksClient::OnTaskSavedCallback callback);
+  void UpdateTaskImpl(const std::string& task_list_id,
+                      const std::string& task_id,
+                      const std::string& title,
+                      TasksClient::OnTaskSavedCallback callback);
+
   void PopulateTasks(base::Time tasks_due_time);
   void PopulateTaskLists(base::Time tasks_due_time);
 
@@ -87,7 +98,11 @@
   int bubble_closed_count_ = 0;
   int completed_tasks_ = 0;
 
-  // If `false` - callbacks executed immediately. If `true` - callbacks get
+  // If `false` - callbacks are executed normally; if `true` - executed with
+  // simulated error (currently works for `AddTask` and `UpdateTask` only).
+  bool run_with_errors_ = false;
+
+  // If `false` - callbacks are executed immediately; if `true` - callbacks get
   // saved to the corresponding list and executed once
   // `RunPending**Callbacks()` is called.
   bool paused_ = false;
diff --git a/ash/assistant/ui/DEPS b/ash/assistant/ui/DEPS
index 484c287..2f6aec8 100644
--- a/ash/assistant/ui/DEPS
+++ b/ash/assistant/ui/DEPS
@@ -52,6 +52,8 @@
 
 specific_include_rules = {
   ".*_unittest\.cc": [
+    "+ash/app_list/test/app_list_test_helper.h",
+    "+ash/app_list/views/search_box_view.h",
     "+ash/assistant/assistant_controller_impl.h",
     "+ash/assistant/assistant_interaction_controller_impl.h",
     "+ash/assistant/assistant_ui_controller.h",
@@ -67,6 +69,7 @@
     "+chromeos/ash/services/assistant/test_support/mock_assistant.h",
     "+chromeos/ui/frame/default_frame_header.h",
     "+chromeos/ui/vector_icons/vector_icons.h",
+    "+components/feature_engagement/test/scoped_iph_feature_list.h",
     "+components/prefs",
     "+components/vector_icons",
     "+testing/gmock",
diff --git a/ash/assistant/ui/main_stage/launcher_search_iph_view.cc b/ash/assistant/ui/main_stage/launcher_search_iph_view.cc
index b387103..96f97ea0 100644
--- a/ash/assistant/ui/main_stage/launcher_search_iph_view.cc
+++ b/ash/assistant/ui/main_stage/launcher_search_iph_view.cc
@@ -16,6 +16,7 @@
 #include "ash/style/typography.h"
 #include "base/functional/bind.h"
 #include "base/i18n/rtl.h"
+#include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/rand_util.h"
 #include "chromeos/ash/services/assistant/public/cpp/assistant_enums.h"
@@ -44,6 +45,8 @@
 
 using QueryType = assistant::LauncherSearchIphQueryType;
 
+std::u16string g_chip_text_for_testing;
+
 constexpr char kLauncherSearchIphQueryTypeHistogramPrefix[] =
     "Assistant.LauncherSearchIphQueryType.";
 constexpr char kLauncherSearchIphQueryFromSearchBox[] = "SearchBox";
@@ -52,7 +55,8 @@
 constexpr int kMainLayoutBetweenChildSpacing = 16;
 constexpr int kActionContainerBetweenChildSpacing = 8;
 
-constexpr int kNumberOfQueryChips = 3;
+constexpr int kNumberOfQueryChipsSearchBox = 3;
+constexpr int kNumberOfQueryChipsAssistantPage = 4;
 
 constexpr gfx::RoundedCornersF kBackgroundRadiiClamshellLTR = {16, 4, 16, 16};
 
@@ -71,14 +75,19 @@
 
 constexpr int kBackgroundRadiusTablet = 16;
 
-std::vector<QueryType> GetQueryChips() {
+std::vector<QueryType> GetRandomizedQueryChips(
+    LauncherSearchIphView::UiLocation location) {
   std::vector<QueryType> chips = {
       QueryType::kWeather,         QueryType::kUnitConversion1,
       QueryType::kUnitConversion2, QueryType::kTranslation,
       QueryType::kDefinition,      QueryType::kCalculation};
-  CHECK_GE(static_cast<int>(chips.size()), kNumberOfQueryChips);
+
+  int num_of_chips = location == LauncherSearchIphView::UiLocation::kSearchBox
+                         ? kNumberOfQueryChipsSearchBox
+                         : kNumberOfQueryChipsAssistantPage;
+  CHECK_GE(static_cast<int>(chips.size()), num_of_chips);
   base::RandomShuffle(chips.begin(), chips.end());
-  chips.resize(kNumberOfQueryChips);
+  chips.resize(num_of_chips);
   return chips;
 }
 
@@ -100,18 +109,28 @@
 }
 
 std::u16string GetQueryText(QueryType type) {
+  if (!g_chip_text_for_testing.empty()) {
+    return g_chip_text_for_testing;
+  }
+
   int id = GetQueryTextId(type);
   return l10n_util::GetStringUTF16(id);
 }
 
 }  // namespace
 
+// static
+void LauncherSearchIphView::SetChipTextForTesting(const std::u16string& text) {
+  g_chip_text_for_testing = text;
+}
+
 LauncherSearchIphView::LauncherSearchIphView(
     Delegate* delegate,
     bool is_in_tablet_mode,
     std::unique_ptr<ScopedIphSession> scoped_iph_session,
     UiLocation location)
     : delegate_(delegate),
+      is_in_tablet_mode_(is_in_tablet_mode),
       scoped_iph_session_(std::move(scoped_iph_session)),
       location_(location) {
   SetID(ViewId::kSelf);
@@ -167,23 +186,7 @@
   actions_container->SetBetweenChildSpacing(
       kActionContainerBetweenChildSpacing);
 
-  CreateQueryChips(actions_container);
-
-  if (location_ == UiLocation::kSearchBox) {
-    views::View* spacer =
-        actions_container->AddChildView(std::make_unique<views::View>());
-    actions_container->SetFlexForView(spacer, 1);
-
-    ash::PillButton* assistant_button =
-        actions_container->AddChildView(std::make_unique<ash::PillButton>(
-            base::BindRepeating(&LauncherSearchIphView::OpenAssistantPage,
-                                weak_ptr_factory_.GetWeakPtr()),
-            l10n_util::GetStringUTF16(
-                IDS_ASH_ASSISTANT_LAUNCHER_SEARCH_IPH_CHIP_ASSISTANT)));
-    assistant_button->SetID(ViewId::kAssistant);
-    assistant_button->SetPillButtonType(
-        PillButton::Type::kDefaultLargeWithoutIcon);
-  }
+  CreateChips(actions_container);
 
   if (is_in_tablet_mode || location_ == UiLocation::kAssistantPage) {
     box_layout_view->SetBackground(views::CreateThemedRoundedRectBackground(
@@ -201,15 +204,28 @@
 
 void LauncherSearchIphView::VisibilityChanged(views::View* starting_from,
                                               bool is_visible) {
-  if (is_visible) {
+  if (is_visible && location_ == UiLocation::kAssistantPage) {
+    // Only shuffle when the IPH is in AssistantPage.
+    // When the IPH is in SearchBox, the chips will be recreated every time.
     ShuffleChipsQuery();
 
+    SetChipsVisibility();
+
     // Label size should be changed. The `PreferredSizeChanged()` in label is
     // not bubbled up to this view, so we need to explicitly call it here.
     PreferredSizeChanged();
   }
 }
 
+void LauncherSearchIphView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+  if (location_ == UiLocation::kAssistantPage) {
+    // Will set visibility of chips in VisibilityChanged().
+    return;
+  }
+
+  SetChipsVisibility();
+}
+
 void LauncherSearchIphView::NotifyAssistantButtonPressedEvent() {
   if (scoped_iph_session_) {
     scoped_iph_session_->NotifyEvent(kIphEventNameAssistantClick);
@@ -220,6 +236,10 @@
   return chips_;
 }
 
+views::View* LauncherSearchIphView::GetAssistantButtonForTesting() {
+  return assistant_button_;
+}
+
 void LauncherSearchIphView::RunLauncherSearchQuery(QueryType query_type) {
   const std::string& location = location_ == UiLocation::kSearchBox
                                     ? kLauncherSearchIphQueryFromSearchBox
@@ -239,9 +259,12 @@
   delegate_->OpenAssistantPage();
 }
 
-void LauncherSearchIphView::CreateQueryChips(views::View* actions_container) {
+void LauncherSearchIphView::CreateChips(
+    views::BoxLayoutView* actions_container) {
+  CHECK(chips_.empty());
+
   int query_chip_view_id = ViewId::kChipStart;
-  for (auto query_type : GetQueryChips()) {
+  for (auto query_type : GetRandomizedQueryChips(location_)) {
     ChipView* chip = actions_container->AddChildView(
         std::make_unique<ChipView>(ChipView::Type::kLarge));
     chip->SetText(GetQueryText(query_type));
@@ -252,11 +275,28 @@
     query_chip_view_id++;
     chips_.emplace_back(chip);
   }
+
+  // If the IPH is in the search box, will add an assistant button.
+  if (location_ == UiLocation::kSearchBox) {
+    views::View* spacer =
+        actions_container->AddChildView(std::make_unique<views::View>());
+    actions_container->SetFlexForView(spacer, 1);
+
+    assistant_button_ =
+        actions_container->AddChildView(std::make_unique<ash::PillButton>(
+            base::BindRepeating(&LauncherSearchIphView::OpenAssistantPage,
+                                weak_ptr_factory_.GetWeakPtr()),
+            l10n_util::GetStringUTF16(
+                IDS_ASH_ASSISTANT_LAUNCHER_SEARCH_IPH_CHIP_ASSISTANT)));
+    assistant_button_->SetID(ViewId::kAssistant);
+    assistant_button_->SetPillButtonType(
+        PillButton::Type::kDefaultLargeWithoutIcon);
+  }
 }
 
 void LauncherSearchIphView::ShuffleChipsQuery() {
   size_t chip_index = 0;
-  for (auto query_type : GetQueryChips()) {
+  for (auto query_type : GetRandomizedQueryChips(location_)) {
     CHECK_LT(chip_index, chips_.size());
     auto chip = chips_[chip_index++];
     chip->SetText(GetQueryText(query_type));
@@ -266,6 +306,48 @@
   }
 }
 
+void LauncherSearchIphView::SetChipsVisibility() {
+  const int iph_width = GetContentsBounds().width();
+  if (iph_width == 0) {
+    return;
+  }
+
+  // Check the PreferredSize of all chips. If the width is wider than the
+  // available width, do not show the last a few query chips but at least show
+  // one chip.
+  int running_width = 0;
+  for (auto chip : chips_) {
+    running_width += chip->GetPreferredSize().width();
+    running_width += kActionContainerBetweenChildSpacing;
+  }
+
+  const auto iph_insets = is_in_tablet_mode_ ? kInnerBackgroundInsetsTablet
+                                             : kInnerBackgroundInsetsClamshell;
+  const int available_width = iph_width - iph_insets.width();
+
+  int assistant_button_width = 0;
+  if (location_ == UiLocation::kSearchBox) {
+    assistant_button_width = assistant_button_->GetPreferredSize().width();
+
+    // Add additional spacing before the `assistant_button_`.
+    // The multiplier `2` is an arbitrary number.
+    running_width += 2 * kActionContainerBetweenChildSpacing;
+    running_width += assistant_button_width;
+  } else {
+    // Subtract the last spacing.
+    running_width -= kActionContainerBetweenChildSpacing;
+  }
+
+  // At least show one chip.
+  chips_[0]->SetVisible(true);
+
+  // Show remaining chips if they fit.
+  for (size_t index = chips_.size() - 1; index > 0; index--) {
+    chips_[index]->SetVisible(running_width <= available_width);
+    running_width -= chips_[index]->GetPreferredSize().width();
+  }
+}
+
 BEGIN_METADATA(LauncherSearchIphView)
 END_METADATA
 
diff --git a/ash/assistant/ui/main_stage/launcher_search_iph_view.h b/ash/assistant/ui/main_stage/launcher_search_iph_view.h
index a60cc65..54b91a5f 100644
--- a/ash/assistant/ui/main_stage/launcher_search_iph_view.h
+++ b/ash/assistant/ui/main_stage/launcher_search_iph_view.h
@@ -17,9 +17,14 @@
 #include "ui/views/controls/styled_label.h"
 #include "ui/views/view.h"
 
+namespace views {
+class BoxLayoutView;
+}  // namespace views
+
 namespace ash {
 
 class ChipView;
+class PillButton;
 
 class LauncherSearchIphView : public views::View {
   METADATA_HEADER(LauncherSearchIphView, views::View)
@@ -57,6 +62,8 @@
     kAssistantPage
   };
 
+  static void SetChipTextForTesting(const std::u16string& text);
+
   LauncherSearchIphView(Delegate* delegate,
                         bool is_in_tablet_mode,
                         std::unique_ptr<ScopedIphSession> scoped_iph_session,
@@ -65,27 +72,34 @@
 
   // views::View:
   void VisibilityChanged(views::View* starting_from, bool is_visible) override;
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
   void NotifyAssistantButtonPressedEvent();
 
   std::vector<raw_ptr<ChipView>> GetChipsForTesting();
+  views::View* GetAssistantButtonForTesting();
 
  private:
   void RunLauncherSearchQuery(assistant::LauncherSearchIphQueryType query_type);
 
   void OpenAssistantPage();
 
-  void CreateQueryChips(views::View* actions_container);
+  void CreateChips(views::BoxLayoutView* actions_container);
 
   void ShuffleChipsQuery();
 
+  void SetChipsVisibility();
+
   raw_ptr<Delegate> delegate_ = nullptr;
 
+  bool is_in_tablet_mode_ = false;
+
   std::unique_ptr<ScopedIphSession> scoped_iph_session_;
 
   UiLocation location_ = UiLocation::kSearchBox;
 
   std::vector<raw_ptr<ChipView>> chips_;
+  raw_ptr<ash::PillButton> assistant_button_ = nullptr;
 
   base::WeakPtrFactory<LauncherSearchIphView> weak_ptr_factory_{this};
 };
diff --git a/ash/assistant/ui/main_stage/launcher_search_iph_view_unittest.cc b/ash/assistant/ui/main_stage/launcher_search_iph_view_unittest.cc
index 39d18e1d..2a35f17 100644
--- a/ash/assistant/ui/main_stage/launcher_search_iph_view_unittest.cc
+++ b/ash/assistant/ui/main_stage/launcher_search_iph_view_unittest.cc
@@ -4,12 +4,20 @@
 
 #include "ash/assistant/ui/main_stage/launcher_search_iph_view.h"
 
+#include "ash/app_list/test/app_list_test_helper.h"
+#include "ash/app_list/views/search_box_view.h"
 #include "ash/assistant/test/assistant_ash_test_base.h"
 #include "ash/assistant/ui/assistant_view_ids.h"
 #include "ash/assistant/ui/main_stage/chip_view.h"
 #include "base/memory/raw_ptr.h"
+#include "base/run_loop.h"
+#include "base/scoped_observation.h"
 #include "base/test/scoped_feature_list.h"
 #include "components/feature_engagement/public/feature_constants.h"
+#include "components/feature_engagement/test/scoped_iph_feature_list.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/view.h"
+#include "ui/views/view_observer.h"
 
 namespace ash {
 
@@ -17,6 +25,38 @@
 
 using LauncherSearchIphViewTest = AssistantAshTestBase;
 
+// TODO(b/317900261): Use ash::ViewDrawnWaiter.
+bool IsDrawn(views::View* view) {
+  return view->IsDrawn() && !view->size().IsEmpty();
+}
+
+class ViewDrawnWaiter : public views::ViewObserver {
+ public:
+  ViewDrawnWaiter() = default;
+  ~ViewDrawnWaiter() override = default;
+
+  void Wait(views::View* view) {
+    if (IsDrawn(view)) {
+      return;
+    }
+
+    view_observer_.Observe(view);
+    wait_loop_.Run();
+  }
+
+ private:
+  // views::ViewObserver:
+  void OnViewBoundsChanged(views::View* view) override {
+    if (IsDrawn(view)) {
+      wait_loop_.Quit();
+    }
+  }
+
+  base::RunLoop wait_loop_;
+  base::ScopedObservation<views::View, views::ViewObserver> view_observer_{
+      this};
+};
+
 }  // namespace
 
 TEST_F(LauncherSearchIphViewTest,
@@ -66,4 +106,190 @@
   EXPECT_NE(queries_1, queries_2);
 }
 
+TEST_F(LauncherSearchIphViewTest, ShowFourChipsInAssistantPage) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      feature_engagement::kIPHLauncherSearchHelpUiFeature);
+
+  ShowAssistantUi();
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      page_view()->GetViewByID(AssistantViewID::kLauncherSearchIph));
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(4, visible_chips);
+}
+
+TEST_F(LauncherSearchIphViewTest, ShowTwoChipsInAssistantPage) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      feature_engagement::kIPHLauncherSearchHelpUiFeature);
+
+  // Set a testing text to only show two chips.
+  std::u16string testing_text = u"Long text for two chips";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  ShowAssistantUi();
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      page_view()->GetViewByID(AssistantViewID::kLauncherSearchIph));
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(2, visible_chips);
+}
+
+TEST_F(LauncherSearchIphViewTest, ShowOneChipsInAssistantPage) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      feature_engagement::kIPHLauncherSearchHelpUiFeature);
+
+  // Set a testing text to only show one chip.
+  std::u16string testing_text = u"Very long text to only show one chip";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  ShowAssistantUi();
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      page_view()->GetViewByID(AssistantViewID::kLauncherSearchIph));
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(1, visible_chips);
+}
+
+TEST_F(LauncherSearchIphViewTest, AtLeastShowOneChipsInAssistantPage) {
+  base::test::ScopedFeatureList scoped_feature_list(
+      feature_engagement::kIPHLauncherSearchHelpUiFeature);
+
+  // Set a testing text to only show one chip even the text cannot fix the
+  // width.
+  std::u16string testing_text =
+      u"Very very very very long text cannot fix the width but still show one "
+      u"chip";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  ShowAssistantUi();
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      page_view()->GetViewByID(AssistantViewID::kLauncherSearchIph));
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(1, visible_chips);
+}
+
+TEST_F(LauncherSearchIphViewTest,
+       ShowIphWhenClickingAssistantButtonInSearchBox) {
+  GetAppListTestHelper()->search_model()->SetWouldTriggerLauncherSearchIph(
+      true);
+  GetAppListTestHelper()->ShowAppList();
+
+  SearchBoxView* search_box_view =
+      GetAppListTestHelper()->GetBubbleSearchBoxView();
+  LeftClickOn(search_box_view->assistant_button());
+
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      search_box_view->GetViewByID(LauncherSearchIphView::ViewId::kSelf));
+  EXPECT_TRUE(!!iph_view);
+  EXPECT_TRUE(iph_view->GetVisible());
+}
+
+TEST_F(LauncherSearchIphViewTest,
+       ShowThreeQueryChipsAndAssistantChipInSearchBox) {
+  // Set a testing text to show three chips.
+  std::u16string testing_text = u"Text";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  GetAppListTestHelper()->search_model()->SetWouldTriggerLauncherSearchIph(
+      true);
+  GetAppListTestHelper()->ShowAppList();
+
+  SearchBoxView* search_box_view =
+      GetAppListTestHelper()->GetBubbleSearchBoxView();
+  LeftClickOn(search_box_view->assistant_button());
+
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      search_box_view->GetViewByID(LauncherSearchIphView::ViewId::kSelf));
+  ViewDrawnWaiter().Wait(iph_view);
+
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(3, visible_chips);
+
+  EXPECT_TRUE(!!iph_view->GetAssistantButtonForTesting());
+  EXPECT_TRUE(iph_view->GetAssistantButtonForTesting()->GetVisible());
+}
+
+TEST_F(LauncherSearchIphViewTest, ShowOneQueryChipAndAssistantChipInSearchBox) {
+  // Set a testing text to only show one chip.
+  std::u16string testing_text = u"Very long text to show only one chip";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  GetAppListTestHelper()->search_model()->SetWouldTriggerLauncherSearchIph(
+      true);
+  GetAppListTestHelper()->ShowAppList();
+
+  SearchBoxView* search_box_view =
+      GetAppListTestHelper()->GetBubbleSearchBoxView();
+  LeftClickOn(search_box_view->assistant_button());
+
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      search_box_view->GetViewByID(LauncherSearchIphView::ViewId::kSelf));
+  ViewDrawnWaiter().Wait(iph_view);
+
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(1, visible_chips);
+
+  EXPECT_TRUE(!!iph_view->GetAssistantButtonForTesting());
+  EXPECT_TRUE(iph_view->GetAssistantButtonForTesting()->GetVisible());
+}
+
+TEST_F(LauncherSearchIphViewTest, AtLeastShowOneQueryChipInSearchBox) {
+  // Set a testing text to only show one chip even the text cannot fix the
+  // width.
+  std::u16string testing_text =
+      u"Very very very very long text cannot fix the width but still show one "
+      u"chip";
+  LauncherSearchIphView::SetChipTextForTesting(testing_text);
+
+  GetAppListTestHelper()->search_model()->SetWouldTriggerLauncherSearchIph(
+      true);
+  GetAppListTestHelper()->ShowAppList();
+
+  SearchBoxView* search_box_view =
+      GetAppListTestHelper()->GetBubbleSearchBoxView();
+  LeftClickOn(search_box_view->assistant_button());
+
+  LauncherSearchIphView* iph_view = static_cast<LauncherSearchIphView*>(
+      search_box_view->GetViewByID(LauncherSearchIphView::ViewId::kSelf));
+  ViewDrawnWaiter().Wait(iph_view);
+
+  int visible_chips = 0;
+  for (auto chip : iph_view->GetChipsForTesting()) {
+    if (chip->GetVisible()) {
+      visible_chips++;
+    }
+  }
+  EXPECT_EQ(1, visible_chips);
+
+  EXPECT_TRUE(!!iph_view->GetAssistantButtonForTesting());
+  EXPECT_TRUE(iph_view->GetAssistantButtonForTesting()->GetVisible());
+}
+
 }  // namespace ash
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.cc b/ash/glanceables/tasks/glanceables_task_view_v2.cc
index 5883455..7ba5c3b 100644
--- a/ash/glanceables/tasks/glanceables_task_view_v2.cc
+++ b/ash/glanceables/tasks/glanceables_task_view_v2.cc
@@ -398,7 +398,8 @@
   UpdateTaskTitleViewForState(TaskTitleViewState::kView);
 
   if (task_id_.empty() || task_title_ != old_title) {
-    save_callback_.Run(task_id_, base::UTF16ToUTF8(task_title_),
+    save_callback_.Run(weak_ptr_factory_.GetWeakPtr(), task_id_,
+                       base::UTF16ToUTF8(task_title_),
                        base::BindOnce(&GlanceablesTaskViewV2::OnSaved,
                                       weak_ptr_factory_.GetWeakPtr()));
     // TODO(b/301253574): introduce "disabled" state for this view to prevent
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.h b/ash/glanceables/tasks/glanceables_task_view_v2.h
index f3cc778..ff621ab 100644
--- a/ash/glanceables/tasks/glanceables_task_view_v2.h
+++ b/ash/glanceables/tasks/glanceables_task_view_v2.h
@@ -47,6 +47,7 @@
   using MarkAsCompletedCallback =
       base::RepeatingCallback<void(const std::string& task_id, bool completed)>;
   using SaveCallback = base::RepeatingCallback<void(
+      base::WeakPtr<GlanceablesTaskViewV2> view,
       const std::string& task_id,
       const std::string& title,
       api::TasksClient::OnTaskSavedCallback callback)>;
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc b/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc
index e764949..a76b9aa4 100644
--- a/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc
+++ b/ash/glanceables/tasks/glanceables_task_view_v2_unittest.cc
@@ -15,6 +15,7 @@
 #include "ash/system/time/calendar_unittest_utils.h"
 #include "ash/test/ash_test_base.h"
 #include "base/functional/callback_helpers.h"
+#include "base/memory/weak_ptr.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
@@ -233,7 +234,8 @@
 }
 
 TEST_F(GlanceablesTaskViewStableLaunchTest, InvokesSaveCallbackAfterAdding) {
-  base::test::TestFuture<const std::string&, const std::string&,
+  base::test::TestFuture<base::WeakPtr<GlanceablesTaskViewV2>,
+                         const std::string&, const std::string&,
                          api::TasksClient::OnTaskSavedCallback>
       future;
 
@@ -252,7 +254,7 @@
   PressAndReleaseKey(ui::VKEY_W);
   PressAndReleaseKey(ui::VKEY_ESCAPE);
 
-  const auto [task_id, title, callback] = future.Take();
+  const auto [task_view, task_id, title, callback] = future.Take();
   EXPECT_TRUE(task_id.empty());
   EXPECT_EQ(title, "New");
 }
@@ -263,7 +265,8 @@
                               /*has_subtasks=*/false, /*has_email_link=*/false,
                               /*has_notes=*/false, /*updated=*/base::Time());
 
-  base::test::TestFuture<const std::string&, const std::string&,
+  base::test::TestFuture<base::WeakPtr<GlanceablesTaskViewV2>,
+                         const std::string&, const std::string&,
                          api::TasksClient::OnTaskSavedCallback>
       future;
 
@@ -283,13 +286,14 @@
   PressAndReleaseKey(ui::VKEY_D);
   PressAndReleaseKey(ui::VKEY_ESCAPE);
 
-  const auto [task_id, title, callback] = future.Take();
+  const auto [task_view, task_id, title, callback] = future.Take();
   EXPECT_EQ(task_id, "task-id");
   EXPECT_EQ(title, "Task title upd");
 }
 
 TEST_F(GlanceablesTaskViewStableLaunchTest, SupportsEditingRightAfterAdding) {
-  base::test::TestFuture<const std::string&, const std::string&,
+  base::test::TestFuture<base::WeakPtr<GlanceablesTaskViewV2>,
+                         const std::string&, const std::string&,
                          api::TasksClient::OnTaskSavedCallback>
       future;
 
@@ -310,7 +314,7 @@
     PressAndReleaseKey(ui::VKEY_ESCAPE);
 
     // Verify that `task_id` is empty after adding a task.
-    auto [task_id, title, callback] = future.Take();
+    auto [task_view, task_id, title, callback] = future.Take();
     EXPECT_TRUE(task_id.empty());
     EXPECT_EQ(title, "New");
 
@@ -332,7 +336,7 @@
     PressAndReleaseKey(ui::VKEY_ESCAPE);
 
     // Verify that `task_id` equals to "task-id" after editing the same task.
-    const auto [task_id, title, callback] = future.Take();
+    const auto [task_view, task_id, title, callback] = future.Take();
     EXPECT_EQ(task_id, "task-id");
     EXPECT_EQ(title, "New 1");
   }
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.cc b/ash/glanceables/tasks/glanceables_tasks_view.cc
index 063275b9..a65171c 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view.cc
@@ -218,10 +218,10 @@
   const auto* const active_task_list = tasks_combobox_model_->GetTaskListAt(
       task_list_combo_box_view_->GetSelectedIndex().value());
   // TODO(b/301253574): make sure there is only one view is in `kEdit` state.
-  pending_new_task_ = task_items_container_view_->AddChildViewAt(
+  auto* const pending_new_task = task_items_container_view_->AddChildViewAt(
       CreateTaskView(active_task_list->id, /*task=*/nullptr),
       /*index=*/0);
-  pending_new_task_->UpdateTaskTitleViewForState(
+  pending_new_task->UpdateTaskTitleViewForState(
       GlanceablesTaskViewV2::TaskTitleViewState::kEdit);
   PreferredSizeChanged();
 }
@@ -347,19 +347,17 @@
 
 void GlanceablesTasksView::SaveTask(
     const std::string& task_list_id,
+    base::WeakPtr<GlanceablesTaskViewV2> view,
     const std::string& task_id,
     const std::string& title,
     api::TasksClient::OnTaskSavedCallback callback) {
   if (task_id.empty()) {
-    // Empty `task_id` applies only for `pending_new_task_`, meaning that the
-    // task has not yet been created. Verify that this task has a non-empty
-    // title, otherwise just delete the view from the scrollable container.
-    CHECK(pending_new_task_);
-    views::View* view_to_delete = pending_new_task_;
-    pending_new_task_ = nullptr;
+    // Empty `task_id` means that the task has not yet been created. Verify that
+    // this task has a non-empty title, otherwise just delete the `view` from
+    // the scrollable container.
     add_new_task_button_->SetState(views::Button::ButtonState::STATE_NORMAL);
-    if (title.empty() && view_to_delete) {
-      task_items_container_view_->RemoveChildViewT(view_to_delete);
+    if (title.empty() && view) {
+      task_items_container_view_->RemoveChildViewT(view.get());
       return;
     }
   }
@@ -367,9 +365,9 @@
   progress_bar_->UpdateProgressBarVisibility(/*visible=*/true);
 
   auto* const client = Shell::Get()->glanceables_controller()->GetTasksClient();
-  auto on_task_saved =
-      base::BindOnce(&GlanceablesTasksView::OnTaskSaved,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback));
+  auto on_task_saved = base::BindOnce(
+      &GlanceablesTasksView::OnTaskSaved, weak_ptr_factory_.GetWeakPtr(),
+      std::move(view), task_id, std::move(callback));
   if (task_id.empty()) {
     client->AddTask(task_list_id, title, std::move(on_task_saved));
   } else {
@@ -378,10 +376,18 @@
 }
 
 void GlanceablesTasksView::OnTaskSaved(
+    base::WeakPtr<GlanceablesTaskViewV2> view,
+    const std::string& task_id,
     api::TasksClient::OnTaskSavedCallback callback,
     const api::Task* task) {
   if (!task) {
-    // TODO(b/301253574): show error message.
+    ShowErrorMessage(u"[l10n] Error");
+    if (task_id.empty() && view) {
+      // Empty `task_id` means that the task has not yet been created. Delete
+      // the corresponding `view` from the scrollable container in case of
+      // error.
+      task_items_container_view_->RemoveChildViewT(view.get());
+    }
   }
   progress_bar_->UpdateProgressBarVisibility(/*visible=*/false);
   std::move(callback).Run(task);
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.h b/ash/glanceables/tasks/glanceables_tasks_view.h
index e14afb16..986e21d1 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.h
+++ b/ash/glanceables/tasks/glanceables_tasks_view.h
@@ -13,6 +13,7 @@
 #include "ash/glanceables/glanceables_metrics.h"
 #include "ash/system/unified/glanceable_tray_child_bubble.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/models/list_model.h"
@@ -96,17 +97,22 @@
                            bool completed);
 
   // Saves the task (either creates or updates the existing one).
+  // `view`     - individual task view which triggered this request.
   // `callback` - done callback passed from an individual task view.
   void SaveTask(const std::string& task_list_id,
+                base::WeakPtr<GlanceablesTaskViewV2> view,
                 const std::string& task_id,
                 const std::string& title,
                 api::TasksClient::OnTaskSavedCallback callback);
 
   // Handles completion of `SaveTask`.
+  // `view`     - individual task view which triggered this request.
   // `callback` - callback passed from an individual task view via `SaveTask`.
   // `task`     - newly created or edited task if the request completes
   //              successfully, `nullptr` otherwise.
-  void OnTaskSaved(api::TasksClient::OnTaskSavedCallback callback,
+  void OnTaskSaved(base::WeakPtr<GlanceablesTaskViewV2> view,
+                   const std::string& task_id,
+                   api::TasksClient::OnTaskSavedCallback callback,
                    const api::Task* task);
 
   // Model for the combobox used to change the active task list.
@@ -129,11 +135,6 @@
   raw_ptr<GlanceablesListFooterView> list_footer_view_ = nullptr;
   raw_ptr<GlanceablesProgressBarView> progress_bar_ = nullptr;
 
-  // Pending new task that was added after pressing `add_new_task_button_`.
-  // Used to limit the number of such views to only one and to remove the view
-  // from `task_items_container_view_` if needed.
-  raw_ptr<GlanceablesTaskViewV2> pending_new_task_ = nullptr;
-
   // Records the time when the bubble was about to request a task list. Used for
   // metrics.
   base::TimeTicks tasks_requested_time_;
diff --git a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
index ee70589..ebb1bd5 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view_unittest.cc
@@ -85,6 +85,11 @@
         base::to_underlying(GlanceablesViewId::kProgressBar)));
   }
 
+  const views::View* GetErrorMessage() const {
+    return views::AsViewClass<views::View>(view_->GetViewByID(
+        base::to_underlying(GlanceablesViewId::kGlanceablesErrorMessageView)));
+  }
+
   api::FakeTasksClient* tasks_client() const {
     return fake_glanceables_tasks_client_.get();
   }
@@ -305,4 +310,59 @@
   EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 0u);
 }
 
+TEST_F(GlanceablesTasksViewTest, HandlesErrorAfterAdding) {
+  tasks_client()->set_paused(true);
+  tasks_client()->set_run_with_errors(true);
+
+  const auto* const task_items_container_view = GetTaskItemsContainerView();
+  ASSERT_TRUE(task_items_container_view);
+
+  EXPECT_EQ(task_items_container_view->children().size(), 2u);
+  EXPECT_FALSE(GetErrorMessage());
+
+  GestureTapOn(GetAddNewTaskButton());
+  PressAndReleaseKey(ui::VKEY_N, ui::EF_SHIFT_DOWN);
+  PressAndReleaseKey(ui::VKEY_E);
+  PressAndReleaseKey(ui::VKEY_W);
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+
+  EXPECT_EQ(task_items_container_view->children().size(), 3u);
+  EXPECT_FALSE(GetErrorMessage());
+
+  EXPECT_EQ(tasks_client()->RunPendingAddTaskCallbacks(), 1u);
+  EXPECT_EQ(task_items_container_view->children().size(), 2u);
+  EXPECT_TRUE(GetErrorMessage());
+}
+
+TEST_F(GlanceablesTasksViewTest, HandlesErrorAfterEditing) {
+  tasks_client()->set_paused(true);
+  tasks_client()->set_run_with_errors(true);
+
+  const auto* const task_items_container_view = GetTaskItemsContainerView();
+  ASSERT_TRUE(task_items_container_view);
+
+  EXPECT_EQ(task_items_container_view->children().size(), 2u);
+  EXPECT_FALSE(GetErrorMessage());
+
+  const auto* const title_label = views::AsViewClass<views::Label>(
+      task_items_container_view->children()[0]->GetViewByID(
+          base::to_underlying(GlanceablesViewId::kTaskItemTitleLabel)));
+  GestureTapOn(title_label);
+  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_SPACE);
+  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_U);
+  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_P);
+  GetEventGenerator()->PressAndReleaseKey(ui::VKEY_D);
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+
+  EXPECT_EQ(task_items_container_view->children().size(), 2u);
+  EXPECT_FALSE(GetErrorMessage());
+
+  EXPECT_EQ(tasks_client()->RunPendingUpdateTaskCallbacks(), 1u);
+  EXPECT_EQ(task_items_container_view->children().size(), 2u);
+  EXPECT_TRUE(GetErrorMessage());
+
+  // TODO(b/308446582): Confirm if the title needs to be reverted back in case
+  // of error.
+}
+
 }  // namespace ash
diff --git a/base/functional/function_ref.h b/base/functional/function_ref.h
index 7cf69d9..911146eb 100644
--- a/base/functional/function_ref.h
+++ b/base/functional/function_ref.h
@@ -62,18 +62,34 @@
 //   }([] { return 42; });
 template <typename R, typename... Args>
 class FunctionRef<R(Args...)> {
- public:
-  // `ABSL_ATTRIBUTE_LIFETIME_BOUND` is important since `FunctionRef` retains
-  // only a reference to `functor`, `functor` must outlive `this`.
   template <typename Functor,
             typename RunType = internal::MakeFunctorTraits<Functor>::RunType>
-    requires std::convertible_to<internal::ExtractReturnType<RunType>, R> &&
-             std::same_as<internal::ExtractArgs<RunType>,
-                          internal::TypeList<Args...>>
+  static constexpr bool kCompatibleFunctor =
+      std::convertible_to<internal::ExtractReturnType<RunType>, R> &&
+      std::same_as<internal::ExtractArgs<RunType>, internal::TypeList<Args...>>;
+
+ public:
+  // `ABSL_ATTRIBUTE_LIFETIME_BOUND` is important; since `FunctionRef` retains
+  // only a reference to `functor`, `functor` must outlive `this`.
+  template <typename Functor>
+    requires kCompatibleFunctor<Functor>
   // NOLINTNEXTLINE(google-explicit-constructor)
   FunctionRef(const Functor& functor ABSL_ATTRIBUTE_LIFETIME_BOUND)
       : wrapped_func_ref_(functor) {}
 
+  // Constructs a reference to the given function pointer. This constructor
+  // serves to exclude this case from lifetime analysis, since the underlying
+  // code pointed to by a function pointer is safe to invoke even if the
+  // lifetime of the pointer provided doesn't outlive us, e.g.:
+  //   `const FunctionRef<void(int)> ref = +[](int i) { ... };`
+  // Without this constructor, the above code would warn about dangling refs.
+  // TODO(pkasting): Also support ptr-to-member-functions; this requires changes
+  // in `absl::FunctionRef` or else rewriting this class to not use that one.
+  template <typename Func>
+    requires kCompatibleFunctor<Func*>
+  // NOLINTNEXTLINE(google-explicit-constructor)
+  FunctionRef(Func* func) : wrapped_func_ref_(func) {}
+
   // Null FunctionRefs are not allowed.
   FunctionRef() = delete;
 
@@ -85,13 +101,6 @@
     return wrapped_func_ref_(std::forward<Args>(args)...);
   }
 
-  absl::FunctionRef<R(Args...)> ToAbsl() const { return wrapped_func_ref_; }
-
-  // In Chrome, converting to `absl::FunctionRef` should be explicitly done
-  // through `ToAbsl()`.
-  template <typename Signature>
-  operator absl::FunctionRef<Signature>() = delete;
-
  private:
   absl::FunctionRef<R(Args...)> wrapped_func_ref_;
 };
diff --git a/base/functional/function_ref_nocompile.nc b/base/functional/function_ref_nocompile.nc
index e64f7e7..92dbda0 100644
--- a/base/functional/function_ref_nocompile.nc
+++ b/base/functional/function_ref_nocompile.nc
@@ -8,20 +8,12 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/function_ref.h"
-#include "third_party/abseil-cpp/absl/functional/function_ref.h"
 
 namespace base {
 
-void NoImplicitVoidify() {
-  // Return values cannot be implicitly discarded.
-  FunctionRef<void()> ref([] { return 0; });  // expected-error {{no matching constructor for initialization of 'FunctionRef<void ()>'}}
-}
-
 void CannotBindFunctionRefs() {
   // `Bind{Once,Repeating}` do not accept `FunctionRef` args due to potential
   // lifetime concerns.
-  [](absl::FunctionRef<void()> ref) { BindOnce(ref); }([] {});       // expected-error@*:* {{Functor may not be a FunctionRef, since that is a non-owning reference that may go out of scope before the callback executes.}}
-  [](absl::FunctionRef<void()> ref) { BindRepeating(ref); }([] {});  // expected-error@*:* {{Functor may not be a FunctionRef, since that is a non-owning reference that may go out of scope before the callback executes.}}
   [](FunctionRef<void()> ref) { BindOnce(ref); }([] {});             // expected-error@*:* {{Functor may not be a FunctionRef, since that is a non-owning reference that may go out of scope before the callback executes.}}
   [](FunctionRef<void()> ref) { BindRepeating(ref); }([] {});        // expected-error@*:* {{Functor may not be a FunctionRef, since that is a non-owning reference that may go out of scope before the callback executes.}}
 }
diff --git a/base/functional/function_ref_unittest.cc b/base/functional/function_ref_unittest.cc
index 89eab67..5acd40a8 100644
--- a/base/functional/function_ref_unittest.cc
+++ b/base/functional/function_ref_unittest.cc
@@ -4,87 +4,142 @@
 
 #include "base/functional/function_ref.h"
 
+#include <stdint.h>
+
+#include <concepts>
+
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/functional/function_ref.h"
 
 namespace base {
 
 namespace {
 
-char Moo(float) {
+char Func(float) {
   return 'a';
 }
 
-struct C {
-  long Method() { return value; }
-  long value;
-};
-
 }  // namespace
 
-TEST(FunctionRefTest, FreeFunction) {
-  [](FunctionRef<char(float)> ref) { EXPECT_EQ('a', ref(1.0)); }(&Moo);
+TEST(FunctionRef, Lambda) {
+  auto add = [](int a, int b) { return a + b; };
+
+  {
+    const FunctionRef<int(int, int)> ref = add;
+    EXPECT_EQ(19, ref(17, 2));
+  }
+
+  {
+    const auto add_const = add;
+    const FunctionRef<int(int, int)> ref = add_const;
+    EXPECT_EQ(19, ref(17, 2));
+  }
 }
 
-TEST(FunctionRefTest, Method) {
-  [](FunctionRef<long(C*)> ref) {
-    C c = {.value = 25L};
-    EXPECT_EQ(25L, ref(&c));
-  }(&C::Method);
-}
-
-TEST(FunctionRefTest, Lambda) {
+TEST(FunctionRef, CapturingLambda) {
   int x = 3;
-  auto lambda = [&x]() { return x; };
-  [](FunctionRef<int()> ref) { EXPECT_EQ(3, ref()); }(lambda);
+  const auto lambda = [&x] { return x; };
+  FunctionRef<int()> ref = lambda;
+  EXPECT_EQ(3, ref());
 }
 
-// Tests for passing a `base::FunctionRef` as an `absl::FunctionRef`.
-TEST(FunctionRefTest, AbslConversion) {
-  // Matching signatures should work.
-  {
-    bool called = false;
-    auto lambda = [&called](float) {
-      called = true;
-      return 'a';
-    };
-    FunctionRef<char(float)> ref(lambda);
-    [](absl::FunctionRef<char(float)> absl_ref) {
-      absl_ref(1.0);
-    }(ref.ToAbsl());
-    EXPECT_TRUE(called);
-  }
-
-  // `absl::FunctionRef` should be able to adapt "similar enough" signatures.
-  {
-    bool called = false;
-    auto lambda = [&called](float) {
-      called = true;
-      return 'a';
-    };
-    FunctionRef<char(float)> ref(lambda);
-    [](absl::FunctionRef<void(float)> absl_ref) {
-      absl_ref(1.0);
-    }(ref.ToAbsl());
-    EXPECT_TRUE(called);
-  }
+TEST(FunctionRef, FunctionPtr) {
+  [](FunctionRef<char(float)> ref) { EXPECT_EQ('a', ref(1.0)); }(&Func);
+  const FunctionRef<char(float)> ref = +[](float) { return 'a'; };
+  EXPECT_EQ('a', ref(1.0f));
 }
 
-// base::FunctionRef allows functors with convertible return types to be
-// adapted.
-TEST(FunctionRefTest, ConvertibleReturnTypes) {
-  // Hopefully this never results in a postmorterm-worthy bug...
+TEST(FunctionRef, Functor) {
+  struct S {
+    int operator()(int x) const { return x; }
+  };
+  const S s;
+  const FunctionRef<int(int)> ref = s;
+  EXPECT_EQ(17, ref(17));
+}
+
+TEST(FunctionRef, Method) {
+  struct S {
+    int Method() const { return value; }
+
+    const int value;
+  };
+  const S s(25);
+  [&s](FunctionRef<int(const S*)> ref) { EXPECT_EQ(25, ref(&s)); }(&S::Method);
+}
+
+// `FunctionRef` allows functors with convertible return types to be adapted.
+TEST(FunctionRef, ConvertibleReturnTypes) {
   {
-    auto lambda = []() -> bool { return true; };
-    [](FunctionRef<int()> ref) { EXPECT_EQ(1, ref()); }(lambda);
+    const auto lambda = [] { return true; };
+    const FunctionRef<int()> ref = lambda;
+    EXPECT_EQ(1, ref());
   }
 
   {
     class Base {};
     class Derived : public Base {};
 
-    auto lambda = []() -> Derived* { return nullptr; };
-    [](FunctionRef<Base*()> ref) { EXPECT_EQ(nullptr, ref()); }(lambda);
+    const auto lambda = []() -> Derived* { return nullptr; };
+    const FunctionRef<const Base*()> ref = lambda;
+    EXPECT_EQ(nullptr, ref());
   }
 }
 
+TEST(FunctionRef, ConstructionFromInexactMatches) {
+  // Lambda.
+  const auto lambda = [](int32_t x) { return x; };
+
+  // Capturing lambda.
+  const auto capturing_lambda = [&](int32_t x) { return lambda(x); };
+
+  // Function pointer.
+  int32_t (*const function_ptr)(int32_t) = +lambda;
+
+  // Functor.
+  struct Functor {
+    int32_t operator()(int32_t x) const { return x; }
+  };
+  const Functor functor;
+
+  // Method.
+  struct Obj {
+    int32_t Method(int32_t x) const { return x; }
+  };
+  int32_t (Obj::*const method)(int32_t) const = &Obj::Method;
+
+  // Each of the objects above should work for a `FunctionRef` with a
+  // convertible return type. In this case, they all return `int32_t`, which
+  // should be seamlessly convertible to `int64_t` below.
+  static_assert(
+      std::constructible_from<FunctionRef<int64_t(int32_t)>, decltype(lambda)>);
+  static_assert(std::constructible_from<FunctionRef<int64_t(int32_t)>,
+                                        decltype(capturing_lambda)>);
+  static_assert(std::constructible_from<FunctionRef<int64_t(int32_t)>,
+                                        decltype(function_ptr)>);
+  static_assert(std::constructible_from<FunctionRef<int64_t(int32_t)>,
+                                        decltype(functor)>);
+  static_assert(
+      std::constructible_from<FunctionRef<int64_t(const Obj*, int32_t)>,
+                              decltype(method)>);
+
+  // It shouldn't be possible to construct a `FunctionRef` from any of the
+  // objects above if we discard the return value.
+  static_assert(
+      !std::constructible_from<FunctionRef<void(int32_t)>, decltype(lambda)>);
+  static_assert(!std::constructible_from<FunctionRef<void(int32_t)>,
+                                         decltype(capturing_lambda)>);
+  static_assert(!std::constructible_from<FunctionRef<void(int32_t)>,
+                                         decltype(function_ptr)>);
+  static_assert(
+      !std::constructible_from<FunctionRef<void(int32_t)>, decltype(functor)>);
+  static_assert(!std::constructible_from<FunctionRef<void(const Obj*, int32_t)>,
+                                         decltype(method)>);
+
+  // It shouldn't be possible to construct a `FunctionRef` from a pointer to a
+  // functor, even with a compatible signature.
+  static_assert(!std::constructible_from<FunctionRef<int32_t(int32_t)>,
+                                         decltype(&functor)>);
+}
+
 }  // namespace base
diff --git a/chrome/VERSION b/chrome/VERSION
index 39baabc..e5bd4748 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=122
 MINOR=0
-BUILD=6211
+BUILD=6212
 PATCH=0
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
index 9664407..70fd243 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
@@ -110,6 +110,10 @@
             new BooleanCachedFieldTrialParameter(
                     ChromeFeatureList.SURFACE_POLISH, SURFACE_POLISH_SCROLLABLE_MVT_PARAM, false);
 
+    public static final BooleanCachedFieldTrialParameter SURFACE_POLISH_USE_MAGIC_SPACE =
+            new BooleanCachedFieldTrialParameter(
+                    ChromeFeatureList.SURFACE_POLISH, "use_magic_space", false);
+
     private static final String STARTUP_UMA_PREFIX = "Startup.Android.";
     private static final String INSTANT_START_SUBFIX = ".Instant";
     private static final String REGULAR_START_SUBFIX = ".NoInstant";
@@ -134,7 +138,7 @@
     /** Returns whether a magic space is enabled on Start surface. */
     public static boolean useMagicSpace() {
         return ChromeFeatureList.sSurfacePolish.isEnabled()
-                && ChromeFeatureList.sMagicStackAndroid.isEnabled()
+                && SURFACE_POLISH_USE_MAGIC_SPACE.getValue()
                 && ChromeFeatureList.sStartSurfaceRefactor.isEnabled();
     }
 
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
index baa6e9b..b57a0869 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceMVTilesTest.java
@@ -174,12 +174,8 @@
     @Test
     @MediumTest
     @Feature({"StartSurface"})
-    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS})
-    @EnableFeatures({
-        ChromeFeatureList.START_SURFACE_REFACTOR,
-        ChromeFeatureList.SURFACE_POLISH,
-        ChromeFeatureList.MAGIC_STACK_ANDROID
-    })
+    @CommandLineFlags.Add({START_SURFACE_TEST_SINGLE_ENABLED_PARAMS + "/use_magic_space/true"})
+    @EnableFeatures({ChromeFeatureList.START_SURFACE_REFACTOR, ChromeFeatureList.SURFACE_POLISH})
     public void testTapMVTilesInSingleSurfaceWithSurfacePolish() {
         testTapMVTilesInSingleSurfaceImpl();
     }
diff --git a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
index 0aedda4..3c75b73 100644
--- a/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
+++ b/chrome/android/features/start_surface/junit/src/org/chromium/chrome/features/start_surface/StartSurfaceMediatorUnitTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.when;
 
 import static org.chromium.chrome.browser.flags.ChromeFeatureList.INSTANT_START;
+import static org.chromium.chrome.features.start_surface.StartSurfaceConfiguration.SURFACE_POLISH_USE_MAGIC_SPACE;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_HEIGHT;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.EXPLORE_SURFACE_COORDINATOR;
 import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_EXPLORE_SURFACE_VISIBLE;
@@ -1878,14 +1879,11 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.START_SURFACE_REFACTOR,
-        ChromeFeatureList.SURFACE_POLISH,
-        ChromeFeatureList.MAGIC_STACK_ANDROID
-    })
+    @EnableFeatures({ChromeFeatureList.START_SURFACE_REFACTOR, ChromeFeatureList.SURFACE_POLISH})
     public void testObserverWithSurfacePolish() {
+        SURFACE_POLISH_USE_MAGIC_SPACE.setForTesting(true);
         Assert.assertTrue(ChromeFeatureList.sSurfacePolish.isEnabled());
-        Assert.assertTrue(ChromeFeatureList.sMagicStackAndroid.isEnabled());
+        Assert.assertTrue(SURFACE_POLISH_USE_MAGIC_SPACE.getValue());
 
         doReturn(false).when(mTabModelSelector).isIncognitoSelected();
         doReturn(mVoiceRecognitionHandler).when(mOmniboxStub).getVoiceRecognitionHandler();
@@ -1954,14 +1952,11 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.START_SURFACE_REFACTOR,
-        ChromeFeatureList.SURFACE_POLISH,
-        ChromeFeatureList.MAGIC_STACK_ANDROID
-    })
+    @EnableFeatures({ChromeFeatureList.START_SURFACE_REFACTOR, ChromeFeatureList.SURFACE_POLISH})
     public void testShowAndOnHideWithSurfacePolish() {
+        SURFACE_POLISH_USE_MAGIC_SPACE.setForTesting(true);
         Assert.assertTrue(ChromeFeatureList.sSurfacePolish.isEnabled());
-        Assert.assertTrue(ChromeFeatureList.sMagicStackAndroid.isEnabled());
+        Assert.assertTrue(SURFACE_POLISH_USE_MAGIC_SPACE.getValue());
 
         doReturn(false).when(mTabModelSelector).isIncognitoSelected();
         doReturn(mVoiceRecognitionHandler).when(mOmniboxStub).getVoiceRecognitionHandler();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index efcac74..b23e8eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -116,6 +116,7 @@
                         StartSurfaceConfiguration.SURFACE_POLISH_MOVE_DOWN_LOGO,
                         StartSurfaceConfiguration.SURFACE_POLISH_LESS_BRAND_SPACE,
                         StartSurfaceConfiguration.SURFACE_POLISH_SCROLLABLE_MVT,
+                        StartSurfaceConfiguration.SURFACE_POLISH_USE_MAGIC_SPACE,
                         TabUiFeatureUtilities.ANIMATION_START_TIMEOUT_MS,
                         TabUiFeatureUtilities.ZOOMING_MIN_MEMORY,
                         TabUiFeatureUtilities.SKIP_SLOW_ZOOMING,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
index e7ad903..78709957 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
@@ -51,7 +51,6 @@
 import org.chromium.components.signin.identitymanager.IdentityMutator;
 import org.chromium.components.signin.identitymanager.PrimaryAccountError;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
-import org.chromium.components.signin.metrics.SigninReason;
 import org.chromium.components.signin.metrics.SignoutDelete;
 import org.chromium.components.signin.metrics.SignoutReason;
 import org.chromium.components.sync.SyncService;
@@ -474,10 +473,6 @@
                     "Signin.SigninCompletedAccessPoint",
                     mSignInState.getAccessPoint(),
                     SigninAccessPoint.MAX);
-            RecordHistogram.recordEnumeratedHistogram(
-                    "Signin.SigninReason",
-                    SigninReason.SIGNIN_PRIMARY_ACCOUNT,
-                    SigninReason.MAX_VALUE + 1);
         }
 
         if (mSignInState.mCallback != null) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
index 1d9e58c..cad07be9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationTest.java
@@ -758,7 +758,6 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/1513445")
     public void testMultipleFresBackButton() throws Exception {
         launchViewIntent(TEST_URL);
         FirstRunActivity firstFreActivity = waitForFirstRunActivity();
@@ -772,6 +771,9 @@
                 0,
                 secondFreData.abortFirstRunExperienceCallback.getCallCount());
 
+        Assert.assertTrue(
+                "FirstRunActivity should intercept back press",
+                secondFreActivity.getOnBackPressedDispatcher().hasEnabledCallbacks());
         TestThreadUtils.runOnUiThreadBlocking(
                 secondFreActivity.getOnBackPressedDispatcher()::onBackPressed);
         secondFreData.abortFirstRunExperienceCallback.waitForCallback(
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 98dd6b3..8e6ebc1 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2363,6 +2363,11 @@
 const FeatureEntry::FeatureParam kSurfacePolish_logo_in_toolbar[] = {
     {"polish_omnibox_color", "true"}};
 
+const FeatureEntry::FeatureParam kSurfacePolish_use_magic_space[] = {
+    {"polish_omnibox_color", "true"},
+    {"move_down_logo", "true"},
+    {"use_magic_space", "true"}};
+
 const FeatureEntry::FeatureVariation kSurfacePolishVariations[] = {
     {"Arm 1: MVP", kSurfacePolish_mvp, std::size(kSurfacePolish_mvp), nullptr},
     {"Arm 2: White Omnibox", kSurfacePolish_white_omnibox,
@@ -2373,6 +2378,8 @@
      std::size(kSurfacePolish_mvp_2row_mvt), nullptr},
     {"Arm 5: Logo in toolbar", kSurfacePolish_logo_in_toolbar,
      std::size(kSurfacePolish_logo_in_toolbar), nullptr},
+    {"Use magic space", kSurfacePolish_use_magic_space,
+     std::size(kSurfacePolish_use_magic_space), nullptr},
 };
 
 const FeatureEntry::FeatureParam kFeedPositionAndroid_push_down_feed_small[] = {
@@ -6948,10 +6955,6 @@
                                     kSurfacePolishVariations,
                                     "SurfacePolish")},
 
-    {"enable-magic-stack-android", flag_descriptions::kMagicStackAndroidName,
-     flag_descriptions::kMagicStackAndroidDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kMagicStackAndroid)},
-
     {"enable-show-ntp-at-startup",
      flag_descriptions::kShowNtpAtStartupAndroidName,
      flag_descriptions::kShowNtpAtStartupAndroidDescription, kOsAndroid,
diff --git a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing.cc b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing.cc
index 48cc644..9d84833d 100644
--- a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing.cc
+++ b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing.cc
@@ -192,14 +192,15 @@
     const std::optional<PerfTraceResult>& result) {
   bool success = result.has_value();
 
-  // TODO(matvore): commitDeviation is still expected by Go clients; update it
-  // to presentDeviation, possibly by populating two fields with the same value
-  // for a grace period.
+  // TODO(matvore): commitDeviation is still expected by Go clients; remove it
+  // once Go clients are expecting presentDeviation.
   custom_trace_result_.emplace(
       base::Value::Dict()
           .Set("success", success)
           .Set("fps", success ? result->fps : 0)
+          .Set("perceivedFps", success ? result->perceived_fps : 0)
           .Set("commitDeviation", success ? result->present_deviation : 0)
+          .Set("presentDeviation", success ? result->present_deviation : 0)
           .Set("renderQuality", success ? result->render_quality : 0));
 }
 
diff --git a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.cc b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.cc
index 790c97f7..52d4e75 100644
--- a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.cc
+++ b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.cc
@@ -92,6 +92,7 @@
   VLOG(1) << "Start tracing.";
 
   frames_.emplace();
+  commit_count_ = 0;
 
   exo::Surface* const surface = exo::GetShellRootSurface(window_);
   DCHECK(surface);
@@ -151,11 +152,14 @@
     return;
   }
 
+  commit_count_++;
   frames_->ListenForPresent(surface);
 }
 
 void ArcAppPerformanceTracingSession::Analyze(base::TimeDelta tracing_period) {
-  if (frames_->presents().size() < 2 || tracing_period <= base::TimeDelta() ||
+  const auto& presents = frames_->presents();
+
+  if (presents.size() < 2 || tracing_period <= base::TimeDelta() ||
       DetectIdle()) {
     Stop(std::nullopt);
     return;
@@ -165,10 +169,9 @@
 
   double vsync_error_deviation_accumulator = 0;
   std::vector<base::TimeDelta> deltas;
-  deltas.reserve(frames_->presents().size() - 1);
+  deltas.reserve(presents.size() - 1);
 
-  for (auto fitr = frames_->presents().begin() + 1;
-       fitr != frames_->presents().end(); fitr++) {
+  for (auto fitr = presents.begin() + 1; fitr != presents.end(); fitr++) {
     const auto frame_delta = base::Microseconds(*fitr - *(fitr - 1));
     deltas.push_back(frame_delta);
 
@@ -186,18 +189,20 @@
     vsync_error_deviation_accumulator +=
         (vsync_error.InMicrosecondsF() * vsync_error.InMicrosecondsF());
   }
-  const double present_deviation =
+  PerfTraceResult result;
+  result.present_deviation =
       sqrt(vsync_error_deviation_accumulator / deltas.size());
 
   std::sort(deltas.begin(), deltas.end());
   // Get 10% and 90% indices.
   const size_t lower_position = deltas.size() / 10;
   const size_t upper_position = deltas.size() - 1 - lower_position;
-  const double render_quality = deltas[lower_position] / deltas[upper_position];
+  result.render_quality = deltas[lower_position] / deltas[upper_position];
 
-  const double fps = deltas.size() / tracing_period.InSecondsF();
+  result.fps = commit_count_ / tracing_period.InSecondsF();
+  result.perceived_fps = presents.size() / tracing_period.InSecondsF();
 
-  Stop(PerfTraceResult{fps, present_deviation, render_quality});
+  Stop(result);
 }
 
 }  // namespace arc
diff --git a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.h b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.h
index 7f9e0b9..865c2bd 100644
--- a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.h
+++ b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_session.h
@@ -27,7 +27,7 @@
 namespace arc {
 
 struct PerfTraceResult {
-  double fps, present_deviation, render_quality;
+  double fps, perceived_fps, present_deviation, render_quality;
 };
 
 using TicksNowCallback = base::RepeatingCallback<base::TimeTicks()>;
@@ -122,6 +122,10 @@
   // Traces and records frame timing.
   std::optional<PresentFramesTracer> frames_;
 
+  // Number of commits that occurred during the trace. This count will include
+  // frames that were not presented by exo e.g. as a result of late commit.
+  uint32_t commit_count_;
+
   TicksNowCallback ticks_now_callback_;
 
   DoneCallback on_done_;
diff --git a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
index 8bc154d..e3dc872 100644
--- a/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
+++ b/chrome/browser/ash/arc/tracing/arc_app_performance_tracing_unittest.cc
@@ -50,9 +50,9 @@
 constexpr int kMillisecondsToFirstFrame = 500;
 
 struct Application {
-  std::string package;
-  std::string activity;
-  std::string name;
+  const char* package;
+  const char* activity;
+  const char* name;
 };
 
 std::string GetStatisticName(const std::string& name,
@@ -79,6 +79,27 @@
   return ReadStatistics(name, kFocusCategory);
 }
 
+constexpr std::initializer_list<Application> kApplications{
+    {"com.mojang.minecraftpe", "com.mojang.minecraftpe.MainActivity",
+     "MinecraftConsumerEdition"},
+    {"com.innersloth.spacemafia",
+     "com.innersloth.spacemafia.EosUnityPlayerActivity", "AmongUs"},
+    {"com.plarium.raidlegends", "com.plarium.unity_app.UnityMainActivity",
+     "RaidLegends"},
+    {"com.valvesoftware.underlords", "com.valvesoftware.underlords.applauncher",
+     "Underlords"},
+    {"com.tocaboca.tocalifeworld", "com.tocaboca.activity.TocaBocaMainActivity",
+     "TocaLife"},
+    {"com.king.candycrushsaga",
+     "com.king.candycrushsaga.CandyCrushSagaActivity", "CandyCrush"},
+    {"com.playrix.homescapes", "com.playrix.homescapes.GoogleActivity",
+     "Homescapes"},
+    {"com.ea.gp.fifamobile", "com.ea.gp.fifamobile.FifaMainActivity",
+     "FIFAMobile"},
+    {"com.miHoYo.GenshinImpact", "com.miHoYo.GetMobileInfo.MainActivity",
+     "GenshinImpact"},
+};
+
 }  // namespace
 
 // BrowserWithTestWindowTest contains required ash/shell support that would not
@@ -97,6 +118,11 @@
 
   // testing::Test:
   void SetUp() override {
+    ForgetAppMetrics(kFocusCategory);
+    for (const auto& app : kApplications) {
+      ForgetAppMetrics(app.name);
+    }
+
     BrowserWithTestWindowTest::SetUp();
 
     arc_test_.SetUp(profile());
@@ -121,6 +147,17 @@
   int64_t task_id = 1;
   std::unique_ptr<exo::Surface> shell_root_surface_;
 
+  void ForgetAppMetrics(const std::string& category) {
+    base::StatisticsRecorder::ForgetHistogramForTesting(
+        GetStatisticName("FPS2", category));
+    base::StatisticsRecorder::ForgetHistogramForTesting(
+        GetStatisticName("PerceivedFPS2", category));
+    base::StatisticsRecorder::ForgetHistogramForTesting(
+        GetStatisticName("CommitDeviation2", category));
+    base::StatisticsRecorder::ForgetHistogramForTesting(
+        GetStatisticName("RenderQuality2", category));
+  }
+
   // Ensures that tracing is ready to begin, which means up to the point that
   // waiting for the delayed start has just begun.
   views::Widget* PrepareArcAppTracing(const std::string& package_name,
@@ -321,7 +358,7 @@
 
   tracing_helper().PlayDefaultSequence(shell_root_surface_.get());
   tracing_helper().FireTimerForTesting();
-  EXPECT_EQ(45L, ReadFocusStatistics("FPS2"));
+  EXPECT_EQ(48L, ReadFocusStatistics("FPS2"));
   EXPECT_EQ(216L, ReadFocusStatistics("CommitDeviation2"));
   EXPECT_EQ(48L, ReadFocusStatistics("RenderQuality2"));
   arc_widget->Close();
@@ -332,61 +369,37 @@
   arc_widget->Close();
 }
 
+TEST_F(ArcAppPerformanceTracingTest, DifferingFPSTypes) {
+  // 32 ms interval is 31.25 FPS.
+  constexpr base::TimeDelta kCommitInterval = base::Milliseconds(32);
+  constexpr int kFrameCount = 100;
+
+  UmaPerfReporting::SetTracingPeriodForTesting(kFrameCount * kCommitInterval);
+  views::Widget* arc_widget = PrepareArcFocusAppTracing();
+
+  tracing_helper().GetTracingSession()->FireTimerForTesting();
+  for (int frame = 0; frame < kFrameCount; frame++) {
+    tracing_helper().AdvanceTickCount(kCommitInterval);
+    // One frame of every ten is missed, so perceived FPS is 28.125.
+    tracing_helper().Commit(
+        shell_root_surface_.get(),
+        (frame % 10) == 0 ? PresentType::kDiscarded : PresentType::kSuccessful);
+  }
+
+  tracing_helper().FireTimerForTesting();
+  EXPECT_EQ(31L, ReadFocusStatistics("FPS2"));
+  EXPECT_EQ(28L, ReadFocusStatistics("PerceivedFPS2"));
+  arc_widget->Close();
+}
+
 TEST_F(ArcAppPerformanceTracingTest, ApplicationStatisticsReported) {
-  std::vector<const Application> applications;
-
-  const Application minecraft = {"com.mojang.minecraftpe",
-                                 "com.mojang.minecraftpe.MainActivity",
-                                 "MinecraftConsumerEdition"};
-  applications.push_back(minecraft);
-
-  const Application among_us = {
-      "com.innersloth.spacemafia",
-      "com.innersloth.spacemafia.EosUnityPlayerActivity", "AmongUs"};
-  applications.push_back(among_us);
-
-  const Application raid_legends = {"com.plarium.raidlegends",
-                                    "com.plarium.unity_app.UnityMainActivity",
-                                    "RaidLegends"};
-  applications.push_back(raid_legends);
-
-  const Application underlords = {"com.valvesoftware.underlords",
-                                  "com.valvesoftware.underlords.applauncher",
-                                  "Underlords"};
-  applications.push_back(underlords);
-
-  const Application toca_life = {"com.tocaboca.tocalifeworld",
-                                 "com.tocaboca.activity.TocaBocaMainActivity",
-                                 "TocaLife"};
-  applications.push_back(toca_life);
-
-  const Application candy_crush = {
-      "com.king.candycrushsaga",
-      "com.king.candycrushsaga.CandyCrushSagaActivity", "CandyCrush"};
-  applications.push_back(candy_crush);
-
-  const Application homescapes = {"com.playrix.homescapes",
-                                  "com.playrix.homescapes.GoogleActivity",
-                                  "Homescapes"};
-  applications.push_back(homescapes);
-
-  const Application fifa_mobile = {"com.ea.gp.fifamobile",
-                                   "com.ea.gp.fifamobile.FifaMainActivity",
-                                   "FIFAMobile"};
-  applications.push_back(fifa_mobile);
-
-  const Application genshin_impact = {"com.miHoYo.GenshinImpact",
-                                      "com.miHoYo.GetMobileInfo.MainActivity",
-                                      "GenshinImpact"};
-  applications.push_back(genshin_impact);
-
-  for (const Application& application : applications) {
+  for (const Application& application : kApplications) {
     views::Widget* const arc_widget =
         StartArcAppTracing(application.package, application.activity);
 
     tracing_helper().PlayDefaultSequence(shell_root_surface_.get());
     tracing_helper().FireTimerForTesting();
-    EXPECT_EQ(45L, ReadStatistics("FPS2", application.name));
+    EXPECT_EQ(48L, ReadStatistics("FPS2", application.name));
     EXPECT_EQ(216L, ReadStatistics("CommitDeviation2", application.name));
     EXPECT_EQ(48L, ReadStatistics("RenderQuality2", application.name));
     arc_widget->Close();
diff --git a/chrome/browser/ash/arc/tracing/test/arc_app_performance_tracing_test_helper.h b/chrome/browser/ash/arc/tracing/test/arc_app_performance_tracing_test_helper.h
index 6bd68977..7632ef9 100644
--- a/chrome/browser/ash/arc/tracing/test/arc_app_performance_tracing_test_helper.h
+++ b/chrome/browser/ash/arc/tracing/test/arc_app_performance_tracing_test_helper.h
@@ -58,8 +58,9 @@
   void PlaySequence(exo::Surface* surface,
                     const std::vector<base::TimeDelta>& deltas);
 
-  // Plays default sequence that has FPS = 45, CommitDeviation = 216 and
-  // RenderQuality = 48% for target tracing period as 1/3 seconds.
+  // Plays default sequence that has PerceivedFPS = FPS = 48,
+  // CommitDeviation = 216 and RenderQuality = 48% for target tracing period as
+  // 1/3 seconds.
   void PlayDefaultSequence(exo::Surface* surface);
 
   // Causes the surface to be committed and its present callback to be invoked.
diff --git a/chrome/browser/ash/arc/tracing/uma_perf_reporting.cc b/chrome/browser/ash/arc/tracing/uma_perf_reporting.cc
index 494f87b..faaf256c 100644
--- a/chrome/browser/ash/arc/tracing/uma_perf_reporting.cc
+++ b/chrome/browser/ash/arc/tracing/uma_perf_reporting.cc
@@ -31,6 +31,14 @@
                               static_cast<int>(std::round(fps)));
 }
 
+void ReportPerceivedFPS(const std::string& category_name,
+                        double perceived_fps) {
+  DCHECK(!category_name.empty());
+  DCHECK_GT(perceived_fps, 0);
+  base::UmaHistogramCounts100(GetHistogramName(category_name, "PerceivedFPS2"),
+                              static_cast<int>(std::round(perceived_fps)));
+}
+
 void ReportCommitDeviation(const std::string& category_name, double error_mcs) {
   DCHECK(!category_name.empty());
   DCHECK_GE(error_mcs, 0);
@@ -82,6 +90,7 @@
             << ", present_deviation: " << result->present_deviation;
 
     ReportFPS(category, result->fps);
+    ReportPerceivedFPS(category, result->perceived_fps);
     ReportCommitDeviation(category, result->present_deviation);
     ReportQuality(category, result->render_quality);
 
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
index e76c09eb..cf2e239 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java
@@ -68,6 +68,10 @@
                         handler.handleBackPress();
                     }
                 };
+        // Update it now since ObservableSupplier posts updates asynchronously.
+        if (handler.getHandleBackPressChangedSupplier().get() != null) {
+            callback.setEnabled(handler.getHandleBackPressChangedSupplier().get());
+        }
         handler.getHandleBackPressChangedSupplier().addObserver(callback::setEnabled);
         dispatcher.addCallback(lifecycleOwner, callback);
     }
diff --git a/chrome/browser/extensions/api/braille_display_private/brlapi_connection.h b/chrome/browser/extensions/api/braille_display_private/brlapi_connection.h
index a69e1af..596294f 100644
--- a/chrome/browser/extensions/api/braille_display_private/brlapi_connection.h
+++ b/chrome/browser/extensions/api/braille_display_private/brlapi_connection.h
@@ -13,15 +13,13 @@
 #include "base/functional/callback_forward.h"
 #include "library_loaders/libbrlapi.h"
 
-namespace extensions {
-namespace api {
-namespace braille_display_private {
+namespace extensions::api::braille_display_private {
 
 // A connection to the brlapi server.  See brlapi.h for more information
 // about the semantics of the methods in this class.
 class BrlapiConnection {
  public:
-  typedef base::RepeatingClosure OnDataReadyCallback;
+  using OnDataReadyCallback = base::RepeatingClosure;
 
   enum ConnectResult {
     CONNECT_ERROR_RETRY,
@@ -75,8 +73,6 @@
   BrlapiConnection() = default;
 };
 
-}  // namespace braille_display_private
-}  // namespace api
-}  // namespace extensions
+}  // namespace extensions::api::braille_display_private
 
 #endif  // CHROME_BROWSER_EXTENSIONS_API_BRAILLE_DISPLAY_PRIVATE_BRLAPI_CONNECTION_H_
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 76f8032a..5e3cdde 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1406,7 +1406,7 @@
   {
     "name": "cros-labs-overview-desk-navigation",
     "owners": [ "richui@chromium.org", "janetmac@chromium.org" ],
-    "expiry_milestone": 122
+    "expiry_milestone": 136
   },
   {
     "name": "cros-labs-window-cycle-shortcut",
@@ -3058,11 +3058,6 @@
     "expiry_milestone": 130
   },
   {
-    "name": "enable-magic-stack-android",
-    "owners": [ "hanxi@chromium.org", "xinyiji@chromium.org", "clank-start@chromium.org" ],
-    "expiry_milestone": 130
-  },
-  {
     "name": "enable-managed-configuration-web-api",
     "owners": [ "apotapchuk@chromium.org" ],
     "expiry_milestone": 92
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 17adec3..f9c1c17 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4096,11 +4096,6 @@
 const char kInterestFeedV2HeartsName[] = "Interest Feed v2 Hearts";
 const char kInterestFeedV2HeartsDescription[] = "Enable hearts on Feedv2.";
 
-const char kMagicStackAndroidName[] = "Magic Stack Android";
-const char kMagicStackAndroidDescription[] =
-    "Show a magic stack which contains a list of modules on Start surface and "
-    "NTPs on Android.";
-
 const char kMediaPickerAdoptionStudyName[] = "Android Media Picker Adoption";
 const char kMediaPickerAdoptionStudyDescription[] =
     "Controls how to launch the Android Media Picker (note: This flag is "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 08d5ccd6f..9280f4e 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2391,9 +2391,6 @@
 extern const char kInterestFeedV2HeartsName[];
 extern const char kInterestFeedV2HeartsDescription[];
 
-extern const char kMagicStackAndroidName[];
-extern const char kMagicStackAndroidDescription[];
-
 extern const char kMediaPickerAdoptionStudyName[];
 extern const char kMediaPickerAdoptionStudyDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 52831238..441cc24 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -219,7 +219,6 @@
     &kInstantStart,
     &kLensCameraAssistedSearch,
     &kLensOnQuickActionSearchWidget,
-    &kMagicStackAndroid,
     &kMultiInstanceApplicationStatusCleanup,
     &kNewTabSearchEngineUrlAndroid,
     &kNotificationPermissionVariant,
@@ -633,10 +632,6 @@
 
 BASE_FEATURE(kInstantStart, "InstantStart", base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kMagicStackAndroid,
-             "MagicStackAndroid",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kMultiInstanceApplicationStatusCleanup,
              "MultiInstanceApplicationStatusCleanup",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 32f40056..55b17fa 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -92,7 +92,6 @@
 BASE_DECLARE_FEATURE(kLensCameraAssistedSearch);
 BASE_DECLARE_FEATURE(kLensOnQuickActionSearchWidget);
 BASE_DECLARE_FEATURE(kLocationBarModelOptimizations);
-BASE_DECLARE_FEATURE(kMagicStackAndroid);
 BASE_DECLARE_FEATURE(kMultiInstanceApplicationStatusCleanup);
 BASE_DECLARE_FEATURE(kNewTabSearchEngineUrlAndroid);
 BASE_DECLARE_FEATURE(kNotificationPermissionVariant);
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 9042e67..28950e4b 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
@@ -274,7 +274,6 @@
     public static final String LENS_ON_QUICK_ACTION_SEARCH_WIDGET = "LensOnQuickActionSearchWidget";
     public static final String LOOKALIKE_NAVIGATION_URL_SUGGESTIONS_UI =
             "LookalikeUrlNavigationSuggestionsUI";
-    public static final String MAGIC_STACK_ANDROID = "MagicStackAndroid";
     public static final String MESSAGES_FOR_ANDROID_ADS_BLOCKED = "MessagesForAndroidAdsBlocked";
     public static final String MESSAGES_FOR_ANDROID_INFRASTRUCTURE =
             "MessagesForAndroidInfrastructure";
@@ -513,7 +512,6 @@
     public static final CachedFlag sInstantStart = new CachedFlag(INSTANT_START, false);
     public static final CachedFlag sHideTabOnTabSwitcher =
             new CachedFlag(HIDE_TAB_ON_TAB_SWITCHER, true);
-    public static final CachedFlag sMagicStackAndroid = new CachedFlag(MAGIC_STACK_ANDROID, false);
     public static final CachedFlag sMultiInstanceApplicationStatusCleanup =
             new CachedFlag(MUlTI_INSTANCE_APPLICATION_STATUS_CLEANUP, true);
     public static final CachedFlag sNewTabSearchEngineUrlAndroid =
@@ -614,7 +612,6 @@
                     sIncognitoReauthenticationForAndroid,
                     sInstantStart,
                     sHideTabOnTabSwitcher,
-                    sMagicStackAndroid,
                     sMultiInstanceApplicationStatusCleanup,
                     sNewTabSearchEngineUrlAndroid,
                     sPaintPreviewNewColdStartHeuristic,
diff --git a/chrome/browser/metrics/family_link_user_metrics_provider.cc b/chrome/browser/metrics/family_link_user_metrics_provider.cc
index 22aad49..47a72af 100644
--- a/chrome/browser/metrics/family_link_user_metrics_provider.cc
+++ b/chrome/browser/metrics/family_link_user_metrics_provider.cc
@@ -21,27 +21,26 @@
 bool FamilyLinkUserMetricsProvider::ProvideHistograms() {
   // This function is called at unpredictable intervals throughout the Chrome
   // session, so guarantee it will never crash.
-
-    ProfileManager* profile_manager = g_browser_process->profile_manager();
-    std::vector<Profile*> profile_list = profile_manager->GetLoadedProfiles();
-    std::vector<AccountInfo> primary_accounts;
-    for (Profile* profile : profile_list) {
+  ProfileManager* profile_manager = g_browser_process->profile_manager();
+  std::vector<Profile*> profile_list = profile_manager->GetLoadedProfiles();
+  std::vector<AccountInfo> primary_accounts;
+  for (Profile* profile : profile_list) {
 #if !BUILDFLAG(IS_ANDROID)
-      // TODO(b/274889379): Mock call to GetBrowserCount().
-      if (!FamilyLinkUserMetricsProvider::
-              skip_active_browser_count_for_unittesting_ &&
-          chrome::GetBrowserCount(profile) == 0) {
-        // The profile is loaded, but there's no opened browser for this
-        // profile.
-        continue;
-      }
-#endif
-      signin::IdentityManager* identity_manager_ =
-          IdentityManagerFactory::GetForProfile(profile);
-      AccountInfo account_info = identity_manager_->FindExtendedAccountInfo(
-          identity_manager_->GetPrimaryAccountInfo(
-              signin::ConsentLevel::kSignin));
-      primary_accounts.push_back(std::move(account_info));
+    // TODO(b/274889379): Mock call to GetBrowserCount().
+    if (!FamilyLinkUserMetricsProvider::
+            skip_active_browser_count_for_unittesting_ &&
+        chrome::GetBrowserCount(profile) == 0) {
+      // The profile is loaded, but there's no opened browser for this
+      // profile.
+      continue;
     }
-    return supervised_user::EmitLogSegmentHistogram(primary_accounts);
+#endif
+    signin::IdentityManager* identity_manager_ =
+        IdentityManagerFactory::GetForProfile(profile);
+    AccountInfo account_info = identity_manager_->FindExtendedAccountInfo(
+        identity_manager_->GetPrimaryAccountInfo(
+            signin::ConsentLevel::kSignin));
+    primary_accounts.push_back(std::move(account_info));
+  }
+  return supervised_user::EmitLogSegmentHistogram(primary_accounts);
 }
diff --git a/chrome/browser/metrics/variations/chrome_variations_service_client.h b/chrome/browser/metrics/variations/chrome_variations_service_client.h
index fed9816c..6f53839 100644
--- a/chrome/browser/metrics/variations/chrome_variations_service_client.h
+++ b/chrome/browser/metrics/variations/chrome_variations_service_client.h
@@ -37,7 +37,8 @@
   TakeSeedFromNativeVariationsSeedStore() override;
   void RemoveGoogleGroupsFromPrefsForDeletedProfiles(
       PrefService* local_state) override;
-  void RegisterLimitedEntropySyntheticTrial(std::string_view group_name);
+  void RegisterLimitedEntropySyntheticTrial(
+      std::string_view group_name) override;
 
  private:
   // variations::VariationsServiceClient:
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index 7830827d..8cb03be2 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -263,6 +263,10 @@
   scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
       on_device_component_manager_;
 
+  // The tab URL provider to use for fetching information for the user's active
+  // tabs. Will be null if the user is off the record.
+  std::unique_ptr<optimization_guide::TabUrlProvider> tab_url_provider_;
+
   // Manages the storing, loading, and fetching of hints.
   std::unique_ptr<optimization_guide::ChromeHintsManager> hints_manager_;
 
@@ -281,10 +285,6 @@
   // behavior.
   std::unique_ptr<optimization_guide::TopHostProvider> top_host_provider_;
 
-  // The tab URL provider to use for fetching information for the user's active
-  // tabs. Will be null if the user is off the record.
-  std::unique_ptr<optimization_guide::TabUrlProvider> tab_url_provider_;
-
   // Manages the model execution. Not created for off the record profiles.
   std::unique_ptr<optimization_guide::ModelExecutionManager>
       model_execution_manager_;
diff --git a/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc b/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
index 2aacd71..873c25b 100644
--- a/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
+++ b/chrome/browser/policy/cloud/user_policy_signin_service_browsertest.cc
@@ -151,7 +151,7 @@
     return new TurnSyncOnHelper(
         profile(), signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER,
         signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT,
-        signin_metrics::Reason::kReauthentication, account_info_.account_id,
+        account_info_.account_id,
         TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT,
         std::make_unique<TestTurnSyncOnHelperDelegate>(this),
         base::DoNothing());
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index c97beb3..6ee90ea5 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -93,7 +93,6 @@
     "crostini_upgrader:closure_compile",
     "emulator:closure_compile",
     "gaia_action_buttons:closure_compile",
-    "internet_config_dialog:closure_compile",
     "login:closure_compile",
     "multidevice_internals:closure_compile",
     "multidevice_setup:closure_compile",
diff --git a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
index 023e5f08..e057646 100644
--- a/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
+++ b/chrome/browser/resources/chromeos/accessibility/.eslintrc.js
@@ -31,6 +31,72 @@
                 'allowExpressions': true,
               },
             ],
+            // https://google.github.io/styleguide/jsguide.html#naming
+            '@typescript-eslint/naming-convention': [
+              'error',
+              {
+                selector: ['class', 'interface', 'typeAlias', 'enum', 'typeParameter'],
+                format: ['StrictPascalCase'],
+                filter: {
+                  regex: '^(' +
+                      // Exclude TypeScript defined interfaces HTMLElementTagNameMap
+                      // and HTMLElementEventMap.
+                      'HTMLElementTagNameMap|HTMLElementEventMap|' +
+                      // Exclude native DOM types which are always named like HTML<Foo>Element.
+                      'HTML[A-Za-z]{0,}Element|' +
+                      // Exclude native DOM interfaces.
+                      'UIEvent|UIEventInit|DOMError|' +
+                      // Exclude the SACache and SACommands classes.
+                      'SACache|SACommands)$',
+                  match: false,
+                },
+              },
+              {
+                selector: 'enumMember',
+                format: ['UPPER_CASE'],
+              },
+              {
+                selector: 'classMethod',
+                format: ['strictCamelCase'],
+                modifiers: ['public'],
+              },
+              {
+                selector: 'classMethod',
+                format: ['strictCamelCase'],
+                modifiers: ['private'],
+                trailingUnderscore: 'allow',
+              },
+              {
+                selector: 'classProperty',
+                format: ['UPPER_CASE'],
+                modifiers: ['private', 'static', 'readonly'],
+              },
+              {
+                selector: 'classProperty',
+                format: ['UPPER_CASE'],
+                modifiers: ['public', 'static', 'readonly'],
+              },
+              {
+                selector: 'classProperty',
+                format: ['camelCase'],
+                modifiers: ['public'],
+              },
+              {
+                selector: 'classProperty',
+                format: ['camelCase'],
+                modifiers: ['private'],
+                trailingUnderscore: 'allow',
+              },
+              {
+                selector: 'parameter',
+                format: ['camelCase'],
+                leadingUnderscore: 'allow',
+              },
+              {
+                selector: 'function',
+                format: ['camelCase'],
+              },
+            ],
         },
       },
     ],
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
index e6620e0..b5ddacc 100644
--- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -14,6 +14,7 @@
 assert(is_chromeos_ash)
 
 accessibility_out_dir = "$root_out_dir/resources/chromeos/accessibility/"
+tsc_preprocess_folder = "$root_gen_dir/chrome/browser/resources/chromeos/accessibility/tsc_preprocessed"
 tsc_out_dir = "$target_gen_dir/tsc"
 
 group("build") {
@@ -170,10 +171,7 @@
 group("unit_tests_js") {
   testonly = true
   if (is_chromeos_ash) {
-    deps = [
-      ":misc_unit_tests_js",
-      "chromevox:chromevox_unit_js_tests",
-    ]
+    deps = [ ":misc_unit_tests_js" ]
   }
 }
 
@@ -183,3 +181,11 @@
   gen_include_files = [ "common/testing/accessibility_test_base.js" ]
   extra_js_files = [ "braille_ime/braille_ime.js" ]
 }
+
+# Copy definitions from the main typescript folder into our local definitions
+# folder. This can only be done once, otherwise we will get collisions, so do
+# this at the very top-level of the build structure.
+copy("preprocess_files_for_ts_build") {
+  sources = [ "//tools/typescript/definitions/chrome_event.d.ts" ]
+  outputs = [ "$tsc_preprocess_folder/definitions/chrome_event.d.ts" ]
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
index abdfc7f3..6a78b77 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
@@ -19,8 +19,24 @@
 
 accessibility_common_dir =
     "$root_out_dir/resources/chromeos/accessibility/accessibility_common"
+tsc_preprocess_folder = "$root_gen_dir/chrome/browser/resources/chromeos/accessibility/tsc_preprocessed"
 tsc_out_dir = "$target_gen_dir/tsc"
 
+misc_sources = [
+  "background.html",
+  "dictation/earcons/audio_end.wav",
+  "dictation/earcons/audio_initiate.wav",
+  "dictation/earcons/null_selection.wav",
+  "dictation/parse/input_text_strategy.js",
+  "dictation/parse/parse_strategy.js",
+  "dictation/parse/pumpkin/pumpkin_constants.js",
+  "dictation/parse/pumpkin_parse_strategy.js",
+  "dictation/parse/sandboxed_pumpkin_tagger.js",
+  "dictation/parse/simple_parse_strategy.js",
+  "dictation/parse/speech_parser.js",
+  "facegaze/camera_stream.html",
+]
+
 # TS files to build.
 ts_modules = [
   "accessibility_common_loader.ts",
@@ -51,35 +67,41 @@
   "magnifier/magnifier.ts",
 ]
 
+ts_definitions = [
+  "../definitions/accessibility_private_mv2.d.ts",
+  "../definitions/audio.d.ts",
+  "../definitions/automation.d.ts",
+  "../definitions/command_line_private.d.ts",
+  "../definitions/extensions.d.ts",
+  "../definitions/extension_types.d.ts",
+  "../definitions/input_ime.d.ts",
+  "../definitions/input_method_private.d.ts",
+  "../definitions/language_settings_private.d.ts",
+  "../definitions/metrics_private.d.ts",
+  "../definitions/runtime.d.ts",
+  "../definitions/settings_private_mv2.d.ts",
+  "../definitions/speech_recognition_private.d.ts",
+  "../definitions/tabs.d.ts",
+  "../definitions/settings_private_mv2.d.ts",
+  "../definitions/accessibility_features_mv2.d.ts",
+  "//tools/typescript/definitions/i18n.d.ts",
+  "//tools/typescript/definitions/windows.d.ts",
+]
+
+# Generated files needed for TS build.
+generated_ts_modules = []
+generated_ts_definitions = []
+
 # JS files needed by the TS compiler.
 js_deps = []
 
 ts_library("ts_build") {
-  # Root dir must be the parent directory so it can reach common.
-  root_dir = "../"
+  # Root dir must be the tsc preprocessed folder, since it will use both checked-in and generated files.
+  root_dir = "$tsc_preprocess_folder"
   out_dir = tsc_out_dir
-  definitions = [
-    "../definitions/accessibility_private_mv2.d.ts",
-    "../definitions/audio.d.ts",
-    "../definitions/automation.d.ts",
-    "../definitions/command_line_private.d.ts",
-    "../definitions/extensions.d.ts",
-    "../definitions/extension_types.d.ts",
-    "../definitions/input_ime.d.ts",
-    "../definitions/input_method_private.d.ts",
-    "../definitions/language_settings_private.d.ts",
-    "../definitions/metrics_private.d.ts",
-    "../definitions/runtime.d.ts",
-    "../definitions/settings_private_mv2.d.ts",
-    "../definitions/speech_recognition_private.d.ts",
-    "../definitions/tabs.d.ts",
-    "../definitions/settings_private_mv2.d.ts",
-    "../definitions/accessibility_features_mv2.d.ts",
-    "//tools/typescript/definitions/i18n.d.ts",
-    "//tools/typescript/definitions/windows.d.ts",
-  ]
+  definitions = ts_definitions + generated_ts_definitions
 
-  in_files = []
+  in_files = generated_ts_modules
   foreach(_js_file, js_deps) {
     in_files += [ "accessibility_common/" + _js_file ]
   }
@@ -88,6 +110,13 @@
   }
 
   tsconfig_base = "../tsconfig.base.json"
+
+  # Targets that need to run before running the TS build.
+  extra_deps = [
+    ":preprocess_files_for_ts_build",
+    "..:preprocess_files_for_ts_build",
+    "../common:preprocess_files_for_ts_build",
+  ]
 }
 
 group("build") {
@@ -106,21 +135,9 @@
     ":ts_build",
     "../common:copied_files",
   ]
-  sources = [
-    "background.html",
-    "dictation/earcons/audio_end.wav",
-    "dictation/earcons/audio_initiate.wav",
-    "dictation/earcons/null_selection.wav",
-    "dictation/parse/input_text_strategy.js",
-    "dictation/parse/parse_strategy.js",
-    "dictation/parse/pumpkin/pumpkin_constants.js",
-    "dictation/parse/pumpkin_parse_strategy.js",
-    "dictation/parse/sandboxed_pumpkin_tagger.js",
-    "dictation/parse/simple_parse_strategy.js",
-    "dictation/parse/speech_parser.js",
-    "facegaze/camera_stream.html",
-  ]
-  sources += filter_include(get_target_outputs(":ts_build"), [ "*.js" ])
+
+  sources =
+      misc_sources + filter_include(get_target_outputs(":ts_build"), [ "*.js" ])
 
   rewrite_rules = [
     rebase_path("$tsc_out_dir/accessibility_common", root_build_dir) + ":",
@@ -254,3 +271,13 @@
     outputs = files_to_extract
   }
 }
+
+# Copy all JS and TS sources to a preprocess folder. All generated TS/JS files
+# will also be copied into this folder, which will allow us to support a TS
+# build that uses both checked-in and generated files.
+copy("preprocess_files_for_ts_build") {
+  sources = misc_sources + ts_modules
+  outputs = [
+    "$tsc_preprocess_folder/accessibility_common/{{source_target_relative}}",
+  ]
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/braille_ime/braille_ime_unittest.js b/chrome/browser/resources/chromeos/accessibility/braille_ime/braille_ime_unittest.js
index b925c8c6..6d4f626 100644
--- a/chrome/browser/resources/chromeos/accessibility/braille_ime/braille_ime_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/braille_ime/braille_ime_unittest.js
@@ -5,7 +5,6 @@
 /**
  * @fileoverview Unit test for the Braille IME.
  */
-GEN_INCLUDE(['../common/testing/accessibility_test_base.js']);
 
 /**
  * Mock Chrome event supporting one listener.
@@ -71,7 +70,7 @@
 /**
  * Test fixture for the braille IME unit test.
  */
-BrailleImeUnitTest = class extends AccessibilityTestBase {
+BrailleImeUnitTest = class extends testing.Test {
   /** @override */
   setUp() {
     super.setUp();
@@ -149,11 +148,11 @@
 
 TEST_F('BrailleImeUnitTest', 'KeysWhenStandardKeyboardDisabled', function() {
   this.activateIme();
-  expectFalse(this.sendKeyDown('KeyF'));
-  expectFalse(this.sendKeyDown('KeyD'));
-  expectFalse(this.sendKeyUp('KeyD'));
-  expectFalse(this.sendKeyUp('KeyF'));
-  expectEquals(0, this.port.messages.length);
+  assertFalse(this.sendKeyDown('KeyF'));
+  assertFalse(this.sendKeyDown('KeyD'));
+  assertFalse(this.sendKeyUp('KeyD'));
+  assertFalse(this.sendKeyUp('KeyF'));
+  assertEquals(0, this.port.messages.length);
 });
 
 TEST_F('BrailleImeUnitTest', 'KeysWhenStandardKeysEnabled', function() {
@@ -162,46 +161,46 @@
   this.onMenuItemActivated.dispatch(ENGINE_ID, this.menuItems[0].id);
   assertTrue(this.menuItems[0].checked);
   // Type the letters 'b' and 'c' and verify the right dots get sent.
-  expectTrue(this.sendKeyDown('KeyF'));
-  expectTrue(this.sendKeyDown('KeyD'));
-  expectTrue(this.sendKeyUp('KeyD'));
-  expectTrue(this.sendKeyUp('KeyF'));
-  expectTrue(this.sendKeyDown('KeyJ'));
-  expectTrue(this.sendKeyDown('KeyF'));
-  expectTrue(this.sendKeyUp('KeyJ'));
-  expectTrue(this.sendKeyUp('KeyF'));
+  assertTrue(this.sendKeyDown('KeyF'));
+  assertTrue(this.sendKeyDown('KeyD'));
+  assertTrue(this.sendKeyUp('KeyD'));
+  assertTrue(this.sendKeyUp('KeyF'));
+  assertTrue(this.sendKeyDown('KeyJ'));
+  assertTrue(this.sendKeyDown('KeyF'));
+  assertTrue(this.sendKeyUp('KeyJ'));
+  assertTrue(this.sendKeyUp('KeyF'));
 
   // Make sure that other keys are not handled, either by themselves or while
   // one of the 'braille keys' is pressed.
-  expectFalse(this.sendKeyDown('KeyX'));
-  expectFalse(this.sendKeyUp('KeyX'));
+  assertFalse(this.sendKeyDown('KeyX'));
+  assertFalse(this.sendKeyUp('KeyX'));
 
-  expectTrue(this.sendKeyDown('KeyS'));   // Dot 3
-  expectFalse(this.sendKeyDown('KeyG'));  // To the right of dot 1.
-  expectTrue(this.sendKeyUp('KeyS'));
-  expectFalse(this.sendKeyUp('KeyG'));
+  assertTrue(this.sendKeyDown('KeyS'));   // Dot 3
+  assertFalse(this.sendKeyDown('KeyG'));  // To the right of dot 1.
+  assertTrue(this.sendKeyUp('KeyS'));
+  assertFalse(this.sendKeyUp('KeyG'));
 
   // Keys like Ctrl L should not be handled, despite L being a dot key.
   var ctrlFlag = {ctrlKey: true};
-  expectFalse(this.sendKeyDown('ControlLeft', ctrlFlag));
-  expectFalse(this.sendKeyDown('KeyL', ctrlFlag));
-  expectFalse(this.sendKeyUp('KeyL', ctrlFlag));
-  expectFalse(this.sendKeyUp('ControlLeft', ctrlFlag));
+  assertFalse(this.sendKeyDown('ControlLeft', ctrlFlag));
+  assertFalse(this.sendKeyDown('KeyL', ctrlFlag));
+  assertFalse(this.sendKeyUp('KeyL', ctrlFlag));
+  assertFalse(this.sendKeyUp('ControlLeft', ctrlFlag));
 
   // Space key by itself should send a blank cell.
-  expectTrue(this.sendKeyDown('Space'));
-  expectTrue(this.sendKeyUp('Space'));
+  assertTrue(this.sendKeyDown('Space'));
+  assertTrue(this.sendKeyUp('Space'));
 
   // Space and braille dots results in no event.
-  expectTrue(this.sendKeyDown('Space'));
-  expectTrue(this.sendKeyDown('KeyF'));
-  expectTrue(this.sendKeyUp('Space'));
-  expectTrue(this.sendKeyUp('KeyF'));
+  assertTrue(this.sendKeyDown('Space'));
+  assertTrue(this.sendKeyDown('KeyF'));
+  assertTrue(this.sendKeyUp('Space'));
+  assertTrue(this.sendKeyUp('KeyF'));
   // Send the braille key first, still no event should be produced.
-  expectTrue(this.sendKeyDown('KeyF'));
-  expectTrue(this.sendKeyDown('Space'));
-  expectTrue(this.sendKeyUp('Space'));
-  expectTrue(this.sendKeyUp('KeyF'));
+  assertTrue(this.sendKeyDown('KeyF'));
+  assertTrue(this.sendKeyDown('Space'));
+  assertTrue(this.sendKeyUp('Space'));
+  assertTrue(this.sendKeyUp('KeyF'));
 
   assertDeepEquals(this.port.messages, [
     {type: 'brailleDots', dots: 0x03},
@@ -217,7 +216,7 @@
   this.onMenuItemActivated.dispatch(ENGINE_ID, this.menuItems[0].id);
   assertTrue(this.menuItems[0].checked);
 
-  expectEquals(undefined, this.sendKeyDown('Backspace'));
+  assertEquals(undefined, this.sendKeyDown('Backspace'));
   assertDeepEquals(
       this.port.messages,
       [{type: 'backspace', requestId: this.lastSentKeyRequestId_ + ''}]);
@@ -226,8 +225,8 @@
     requestId: this.lastSentKeyRequestId_ + '',
     result: true,
   });
-  expectEquals(this.lastSentKeyRequestId_, this.lastHandledKeyRequestId_);
-  expectTrue(this.lastHandledKeyResult_);
+  assertEquals(this.lastSentKeyRequestId_, this.lastHandledKeyRequestId_);
+  assertTrue(this.lastHandledKeyResult_);
 });
 
 TEST_F('BrailleImeUnitTest', 'UseStandardKeyboardSettingPreserved', function() {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index acd7000..1e536d11 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -410,36 +410,6 @@
   }
 
   # These tests can run in a pure V8 renderer (e.g. no DOM).
-  js2gtest("chromevox_unit_js_tests") {
-    test_type = "unit"
-    sources = [
-      "background/braille/expanding_braille_translator_test.js",
-      "background/braille/pan_strategy_test.js",
-      "background/editing/editable_text_base_test.js",
-      "background/phonetic_data_test.js",
-      "common/key_sequence_test.js",
-      "common/spannable_test.js",
-      "testing/mock_feedback_test.js",
-    ]
-    gen_include_files = [ "../common/testing/accessibility_test_base.js" ]
-    extra_js_files = [
-      "../common/testing/accessibility_test_base.js",
-      "../common/testing/assert_additions.js",
-      "../common/testing/callback_helper.js",
-      "background/braille/expanding_braille_translator.js",
-      "background/braille/pan_strategy.js",
-      "background/braille/spans.js",
-      "background/braille/liblouis.js",
-      "background/editing/editable_text_base.js",
-      "background/phonetic_data.js",
-      "common/key_sequence.js",
-      "common/msgs.js",
-      "common/spannable.js",
-      "testing/fake_dom.js",
-      "testing/mock_feedback.js",
-      "third_party/tamachiyomi/ja_phonetic_data.js",
-    ]
-  }
 
   # These tests need a full extension renderer.
   js2gtest("chromevox_extension_js_tests") {
@@ -480,6 +450,20 @@
       "panel/panel_test_base.js",
       "panel/tutorial_test.js",
     ]
+
+    # These tests need support for async/await (b/317173285).
+    sources += [
+      "background/braille/expanding_braille_translator_test.js",
+      "background/braille/pan_strategy_test.js",
+
+      # TODO(anastasi): Determine why this test suite is flaky and re-enable.
+      # "background/editing/editable_text_base_test.js",
+      "background/phonetic_data_test.js",
+      "common/key_sequence_test.js",
+      "common/spannable_test.js",
+      "testing/mock_feedback_test.js",
+    ]
+
     gen_include_files = [
       "../common/testing/accessibility_test_base.js",
       "../common/testing/assert_additions.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/expanding_braille_translator_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/expanding_braille_translator_test.js
index e04b227..af06b23 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/expanding_braille_translator_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/expanding_braille_translator_test.js
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../../testing/chromevox_e2e_test_base.js']);
 
 /**
  * Test fixture.
  */
-ChromeVoxExpandingBrailleTranslatorUnitTest =
-    class extends AccessibilityTestBase {
+ChromeVoxExpandingBrailleTranslatorUnitTest = class extends ChromeVoxE2ETest {
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
@@ -19,7 +18,12 @@
           'ExpandingBrailleTranslator',
           '/chromevox/background/braille/expanding_braille_translator.js'),
       importModule(
-          ['ExtraCellsSpan', 'ValueSelectionSpan', 'ValueSpan'],
+          [
+            'BrailleTextStyleSpan',
+            'ExtraCellsSpan',
+            'ValueSelectionSpan',
+            'ValueSpan',
+          ],
           '/chromevox/background/braille/spans.js'),
       importModule('LibLouis', '/chromevox/background/braille/liblouis.js'),
       importModule('Spannable', '/chromevox/common/spannable.js'),
@@ -27,12 +31,6 @@
   }
 };
 
-/** @override */
-ChromeVoxExpandingBrailleTranslatorUnitTest.prototype.extraLibraries = [
-  '../../../common/testing/assert_additions.js',
-  '../../testing/fake_dom.js',
-];
-
 /**
  * An implementation of {@link LibLouis.Translator} whose translation
  * output is an array buffer of the same byte length as the input and where
@@ -86,7 +84,7 @@
   }
 }
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxExpandingBrailleTranslatorUnitTest', 'TranslationError',
     function() {
       const text = new Spannable('error ok', new ValueSpan());
@@ -268,7 +266,7 @@
 
 const TEXT = 'Hello, world!';
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxExpandingBrailleTranslatorUnitTest', 'successfulTranslations',
     function() {
       /**
@@ -339,7 +337,7 @@
       assertEquals(totalExpectedTranslationTests, totalRunTranslationTests);
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxExpandingBrailleTranslatorUnitTest', 'StyleTranslations',
     function() {
       const formTypeMap = {};
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/pan_strategy_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/pan_strategy_test.js
index d9940331..19cd8d2 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/pan_strategy_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/braille/pan_strategy_test.js
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../../testing/chromevox_e2e_test_base.js']);
 
 /**
  * Test fixture.
  */
-ChromeVoxPanStrategyUnitTest = class extends AccessibilityTestBase {
+ChromeVoxPanStrategyUnitTest = class extends ChromeVoxE2ETest {
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
@@ -21,13 +21,6 @@
   }
 };
 
-/** @override */
-ChromeVoxPanStrategyUnitTest.prototype.extraLibraries = [
-  '../../../common/testing/assert_additions.js',
-  '../../testing/fake_dom.js',
-];
-
-
 /**
  * Creates an array buffer based off of the passed in content.
  * Note: Input should be a string of numbers, spaces will turn to 0's.
@@ -42,7 +35,7 @@
   return result;
 }
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'FixedPanning', function() {
+AX_TEST_F('ChromeVoxPanStrategyUnitTest', 'FixedPanning', function() {
   const panner = new PanStrategy();
   panner.setPanStrategy(false);
 
@@ -95,100 +88,102 @@
   assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
 });
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'WrappedPanningSingleLine', function() {
-  const panner = new PanStrategy();
-  panner.setPanStrategy(true);
+AX_TEST_F(
+    'ChromeVoxPanStrategyUnitTest', 'WrappedPanningSingleLine', function() {
+      const panner = new PanStrategy();
+      panner.setPanStrategy(true);
 
-  // 30 cells with blank cells at positions 8, 22 and 26.
-  const content = createArrayBuffer('11234567 9112345678911 345 789');
-  panner.setContent('a', content, [], 0);
-  assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
-  assertFalse(panner.next());
-  assertFalse(panner.previous());
+      // 30 cells with blank cells at positions 8, 22 and 26.
+      const content = createArrayBuffer('11234567 9112345678911 345 789');
+      panner.setContent('a', content, [], 0);
+      assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
+      assertFalse(panner.next());
+      assertFalse(panner.previous());
 
-  panner.setDisplaySize(1, 10);
-  assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('11234567  '),
-      panner.getCurrentBrailleViewportContents());
-  assertTrue(panner.next());
-  assertEqualsJSON({firstRow: 1, lastRow: 1}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('9112345678'),
-      panner.getCurrentBrailleViewportContents());
-  assertTrue(panner.next());
-  assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('911 345   '),
-      panner.getCurrentBrailleViewportContents());
-  assertTrue(panner.next());
-  assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('789'), panner.getCurrentBrailleViewportContents());
-  assertFalse(panner.next());
-  assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
-  assertTrue(panner.previous());
-  assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
-  assertTrue(panner.previous());
-  assertEqualsJSON({firstRow: 1, lastRow: 1}, panner.viewPort);
-  assertTrue(panner.previous());
-  assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
-  assertFalse(panner.previous());
+      panner.setDisplaySize(1, 10);
+      assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('11234567  '),
+          panner.getCurrentBrailleViewportContents());
+      assertTrue(panner.next());
+      assertEqualsJSON({firstRow: 1, lastRow: 1}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('9112345678'),
+          panner.getCurrentBrailleViewportContents());
+      assertTrue(panner.next());
+      assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('911 345   '),
+          panner.getCurrentBrailleViewportContents());
+      assertTrue(panner.next());
+      assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('789'), panner.getCurrentBrailleViewportContents());
+      assertFalse(panner.next());
+      assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
+      assertTrue(panner.previous());
+      assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
+      assertTrue(panner.previous());
+      assertEqualsJSON({firstRow: 1, lastRow: 1}, panner.viewPort);
+      assertTrue(panner.previous());
+      assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
+      assertFalse(panner.previous());
 
-  panner.setContent('a', content, [], 21);
-  assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('911 345   '),
-      panner.getCurrentBrailleViewportContents());
+      panner.setContent('a', content, [], 21);
+      assertEqualsJSON({firstRow: 2, lastRow: 2}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('911 345   '),
+          panner.getCurrentBrailleViewportContents());
 
-  panner.setContent('a', content, [], 30);
-  assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('789'), panner.getCurrentBrailleViewportContents());
+      panner.setContent('a', content, [], 30);
+      assertEqualsJSON({firstRow: 3, lastRow: 3}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('789'), panner.getCurrentBrailleViewportContents());
 
-  panner.setDisplaySize(1, 8);
-  assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('11234567'),
-      panner.getCurrentBrailleViewportContents());
-});
+      panner.setDisplaySize(1, 8);
+      assertEqualsJSON({firstRow: 0, lastRow: 0}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('11234567'),
+          panner.getCurrentBrailleViewportContents());
+    });
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'WrappedPanningMultiline', function() {
-  const panner = new PanStrategy();
-  panner.setPanStrategy(true);
+AX_TEST_F(
+    'ChromeVoxPanStrategyUnitTest', 'WrappedPanningMultiline', function() {
+      const panner = new PanStrategy();
+      panner.setPanStrategy(true);
 
-  // 30 cells with blank cells at positions 8, 22 and 26.
-  const content = createArrayBuffer('11234567 9112345678911 345 789');
-  panner.setContent('a', content, [], 0);
+      // 30 cells with blank cells at positions 8, 22 and 26.
+      const content = createArrayBuffer('11234567 9112345678911 345 789');
+      panner.setContent('a', content, [], 0);
 
-  panner.setDisplaySize(2, 10);
-  assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('11234567  9112345678'),
-      panner.getCurrentBrailleViewportContents());
-  assertTrue(panner.next());
-  assertEqualsJSON({firstRow: 2, lastRow: 3}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('911 345   789'),
-      panner.getCurrentBrailleViewportContents());
-  assertFalse(panner.next());
-  assertEqualsJSON({firstRow: 2, lastRow: 3}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('911 345   789'),
-      panner.getCurrentBrailleViewportContents());
-  assertTrue(panner.previous());
-  assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('11234567  9112345678'),
-      panner.getCurrentBrailleViewportContents());
-  assertFalse(panner.previous());
-  assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
-  assertArrayBuffersEquals(
-      createArrayBuffer('11234567  9112345678'),
-      panner.getCurrentBrailleViewportContents());
-});
+      panner.setDisplaySize(2, 10);
+      assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('11234567  9112345678'),
+          panner.getCurrentBrailleViewportContents());
+      assertTrue(panner.next());
+      assertEqualsJSON({firstRow: 2, lastRow: 3}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('911 345   789'),
+          panner.getCurrentBrailleViewportContents());
+      assertFalse(panner.next());
+      assertEqualsJSON({firstRow: 2, lastRow: 3}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('911 345   789'),
+          panner.getCurrentBrailleViewportContents());
+      assertTrue(panner.previous());
+      assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('11234567  9112345678'),
+          panner.getCurrentBrailleViewportContents());
+      assertFalse(panner.previous());
+      assertEqualsJSON({firstRow: 0, lastRow: 1}, panner.viewPort);
+      assertArrayBuffersEquals(
+          createArrayBuffer('11234567  9112345678'),
+          panner.getCurrentBrailleViewportContents());
+    });
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'FixedSetContent', function() {
+AX_TEST_F('ChromeVoxPanStrategyUnitTest', 'FixedSetContent', function() {
   const panner = new PanStrategy();
   panner.setPanStrategy(false);
 
@@ -203,7 +198,7 @@
   assertArraysEquals(expectedMappingValue, panner.brailleToText);
 });
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'WrappedSetContent', function() {
+AX_TEST_F('ChromeVoxPanStrategyUnitTest', 'WrappedSetContent', function() {
   const panner = new PanStrategy();
   panner.setPanStrategy(true);
 
@@ -257,7 +252,7 @@
   assertArraysEquals(expectedMappingValue, panner.brailleToText);
 });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPanStrategyUnitTest', 'getCurrentTextViewportContents',
     function() {
       const panner = new PanStrategy();
@@ -285,26 +280,27 @@
       assertEquals('789', panner.getCurrentTextViewportContents());
     });
 
-TEST_F('ChromeVoxPanStrategyUnitTest', 'WrappedUnwrappedCursors', function() {
-  const panner = new PanStrategy();
-  panner.setPanStrategy(true);
+AX_TEST_F(
+    'ChromeVoxPanStrategyUnitTest', 'WrappedUnwrappedCursors', function() {
+      const panner = new PanStrategy();
+      panner.setPanStrategy(true);
 
-  // 30 cells with blank cells at positions 8, 22 and 26.
-  const content = createArrayBuffer('11234567 9112345678911 345 789');
+      // 30 cells with blank cells at positions 8, 22 and 26.
+      const content = createArrayBuffer('11234567 9112345678911 345 789');
 
-  panner.setCursor(1, 3);
-  panner.setContent('a', content, [], 0);
-  panner.setDisplaySize(2, 10);
-  assertEqualsJSON({start: 1, end: 3}, panner.getCursor());
-  assertEqualsJSON({start: 1, end: 3}, panner.wrappedCursor_);
+      panner.setCursor(1, 3);
+      panner.setContent('a', content, [], 0);
+      panner.setDisplaySize(2, 10);
+      assertEqualsJSON({start: 1, end: 3}, panner.getCursor());
+      assertEqualsJSON({start: 1, end: 3}, panner.wrappedCursor_);
 
-  panner.setCursor(5, 10);
-  panner.setContent('a', content, [], 0);
-  assertEqualsJSON({start: 5, end: 10}, panner.getCursor());
-  assertEqualsJSON({start: 5, end: 11}, panner.wrappedCursor_);
+      panner.setCursor(5, 10);
+      panner.setContent('a', content, [], 0);
+      assertEqualsJSON({start: 5, end: 10}, panner.getCursor());
+      assertEqualsJSON({start: 5, end: 11}, panner.wrappedCursor_);
 
-  panner.setCursor(9, 9);
-  panner.setContent('a', content, [], 0);
-  assertEqualsJSON({start: 9, end: 9}, panner.getCursor());
-  assertEqualsJSON({start: 10, end: 11}, panner.wrappedCursor_);
-});
+      panner.setCursor(9, 9);
+      panner.setContent('a', content, [], 0);
+      assertEqualsJSON({start: 9, end: 9}, panner.getCursor());
+      assertEqualsJSON({start: 10, end: 11}, panner.wrappedCursor_);
+    });
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
index da49987..d308c814 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/editing/editable_text_base_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../../testing/chromevox_e2e_test_base.js']);
 
 /**
  * A TTS class implementing speak and stop methods intended only for testing.
@@ -84,7 +84,7 @@
 };
 
 /** Test fixture. */
-ChromeVoxEditableTextUnitTest = class extends AccessibilityTestBase {
+ChromeVoxEditableTextUnitTest = class extends ChromeVoxE2ETest {
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
@@ -101,11 +101,12 @@
           'AutomationEditableText',
           '/chromevox/background/editing/editable_text.js'),
       importModule(
-          ['ChromeVoxEditableTextBase', 'TextChangedEvent'],
+          ['ChromeVoxEditableTextBase', 'TextChangeEvent'],
           '/chromevox/background/editing/editable_text_base.js'),
       importModule(
           'TypingEchoState', '/chromevox/background/editing/typing_echo.js'),
-      importModule('TtsInterface', '/chromevox/common/tts_interface.js'),
+      importModule('TtsInterface', '/chromevox/background/tts_interface.js'),
+      importModule('Msgs', '/chromevox/common/msgs.js'),
       importModule('LocalStorage', '/common/local_storage.js'),
     ]);
 
@@ -128,10 +129,6 @@
   }
 };
 
-ChromeVoxEditableTextUnitTest.prototype.extraLibraries = [
-  '../../../common/testing/assert_additions.js',
-];
-
 function createEditableText(value, start, end, isPassword, tts) {
   const fakeNode = {state: {editable: true}};
   const editable = new AutomationEditableText(fakeNode);
@@ -144,7 +141,7 @@
   return editable;
 }
 
-TEST_F('ChromeVoxEditableTextUnitTest', 'CursorNavigation', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'CursorNavigation', function() {
   var tts = new TestTts();
   var obj = createEditableText('Hello', 0, 0, false, tts);
 
@@ -162,7 +159,7 @@
 });
 
 /** Test typing words. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'TypingWords', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'TypingWords', function() {
   var tts = new TestTts();
   var obj = createEditableText('', 0, 0, false, tts);
   obj.changed(new TextChangeEvent('H', 1, 1));
@@ -232,7 +229,7 @@
 });
 
 /** Test selection. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'Selection', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'Selection', function() {
   var tts = new TestTts();
   var obj = createEditableText('Hello, world.', 0, 0, false, tts);
   obj.changed(new TextChangeEvent('Hello, world.', 0, 1));
@@ -273,7 +270,7 @@
  * address bar, and it's being autocompleted. Sometimes it's autocompleted
  * as they type, sometimes there's a short delay.
  */
-TEST_F('ChromeVoxEditableTextUnitTest', 'Autocomplete', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'Autocomplete', function() {
   var tts = new TestTts();
   var obj = createEditableText('', 0, 0, false, tts);
 
@@ -323,7 +320,7 @@
 /**
  * Test a few common scenarios where text is replaced.
  */
-TEST_F('ChromeVoxEditableTextUnitTest', 'ReplacingText', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'ReplacingText', function() {
   // Initial value is Alabama.
   var tts = new TestTts();
   var obj = createEditableText('Alabama', 0, 0, false, tts);
@@ -362,7 +359,7 @@
 /**
  * Test feedback when text changes in a long sentence.
  */
-TEST_F('ChromeVoxEditableTextUnitTest', 'ReplacingLongText', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'ReplacingLongText', function() {
   var tts = new TestTts();
   var obj = createEditableText(
       'I love deadlines. I like the whooshing sound they make as they fly by.',
@@ -377,7 +374,7 @@
 });
 
 /** Tests character echo. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'CharacterEcho', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'CharacterEcho', function() {
   LocalStorage.set('typingEcho', TypingEchoState.CHARACTER);
   var tts = new TestTts();
   var obj = createEditableText('', 0, 0, false, tts);
@@ -401,41 +398,42 @@
 
 
 /** Tests character echo in auto complete text fields. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'CharEchoInAutoComplete', function() {
-  var tts = new TestTts();
-  var url = 'chromevox.com';
-  var obj = createEditableText(url, 1, 13, false, tts);
+AX_TEST_F(
+    'ChromeVoxEditableTextUnitTest', 'CharEchoInAutoComplete', function() {
+      var tts = new TestTts();
+      var url = 'chromevox.com';
+      var obj = createEditableText(url, 1, 13, false, tts);
 
-  // This simulates a user typing into an auto complete text field one character
-  // at a time. The selection is the completion and we toggle between various
-  // typing echo options.
-  LocalStorage.set('typingEcho', TypingEchoState.CHARACTER);
-  obj.changed(new TextChangeEvent(url, 2, 13));
-  LocalStorage.set('typingEcho', TypingEchoState.NONE);
-  obj.changed(new TextChangeEvent(url, 3, 13));
-  LocalStorage.set('typingEcho', TypingEchoState.CHARACTER_AND_WORD);
-  obj.changed(new TextChangeEvent(url, 4, 13));
-  LocalStorage.set('typingEcho', TypingEchoState.WORD);
-  obj.changed(new TextChangeEvent(url, 5, 13));
+      // This simulates a user typing into an auto complete text field one
+      // character at a time. The selection is the completion and we toggle
+      // between various typing echo options.
+      LocalStorage.set('typingEcho', TypingEchoState.CHARACTER);
+      obj.changed(new TextChangeEvent(url, 2, 13));
+      LocalStorage.set('typingEcho', TypingEchoState.NONE);
+      obj.changed(new TextChangeEvent(url, 3, 13));
+      LocalStorage.set('typingEcho', TypingEchoState.CHARACTER_AND_WORD);
+      obj.changed(new TextChangeEvent(url, 4, 13));
+      LocalStorage.set('typingEcho', TypingEchoState.WORD);
+      obj.changed(new TextChangeEvent(url, 5, 13));
 
-  // The characters should only be read for the typing echo modes containing a
-  // character. They are commented out below when unexpected to make the test
-  // clearer to read.
-  assertEqualStringArrays(
-      [
-        'h',
-        url.slice(2),
-        /* 'r', */ url.slice(3),
-        'o',
-        url.slice(4),
-        /* 'm', */ url.slice(5),
-      ],
-      tts.get());
-});
+      // The characters should only be read for the typing echo modes containing
+      // a character. They are commented out below when unexpected to make the
+      // test clearer to read.
+      assertEqualStringArrays(
+          [
+            'h',
+            url.slice(2),
+            /* 'r', */ url.slice(3),
+            'o',
+            url.slice(4),
+            /* 'm', */ url.slice(5),
+          ],
+          tts.get());
+    });
 
 
 /** Tests word echo. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'WordEcho', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'WordEcho', function() {
   LocalStorage.set('typingEcho', TypingEchoState.WORD);
   var tts = new TestTts();
   var obj = createEditableText('', 0, 0, false, tts);
@@ -457,7 +455,7 @@
 
 
 /** Tests no echo. */
-TEST_F('ChromeVoxEditableTextUnitTest', 'NoEcho', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'NoEcho', function() {
   LocalStorage.set('typingEcho', TypingEchoState.NONE);
   var tts = new TestTts();
   var obj = createEditableText('', 0, 0, false, tts);
@@ -478,7 +476,7 @@
 });
 
 /** Tests normalization of TextChangeEvent's */
-TEST_F('ChromeVoxEditableTextUnitTest', 'TextChangeEvent', function() {
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'TextChangeEvent', function() {
   var event1 = new TextChangeEvent('foo', 0, 1, true);
   var event2 = new TextChangeEvent('foo', 1, 0, true);
   var event3 = new TextChangeEvent('foo', 1, 1, true);
@@ -493,17 +491,18 @@
   assertEquals(1, event3.end);
 });
 
-TEST_F('ChromeVoxEditableTextUnitTest', 'TypingNonBreakingSpaces', function() {
-  var tts = new TestTts();
-  var obj = createEditableText('Hello', 0, 0, false, tts);
+AX_TEST_F(
+    'ChromeVoxEditableTextUnitTest', 'TypingNonBreakingSpaces', function() {
+      var tts = new TestTts();
+      var obj = createEditableText('Hello', 0, 0, false, tts);
 
-  obj.changed(new TextChangeEvent('h', 1, 1));
-  obj.changed(new TextChangeEvent('hi', 2, 2));
-  obj.changed(new TextChangeEvent('hi\u00a0', 3, 3));
-  obj.changed(new TextChangeEvent('hi t', 4, 4));
-  assertEqualStringArrays(['h', 'i', 'hi ', 't'], tts.get());
-});
-TEST_F('ChromeVoxEditableTextUnitTest', 'DoesNotSpeakDeleted', function() {
+      obj.changed(new TextChangeEvent('h', 1, 1));
+      obj.changed(new TextChangeEvent('hi', 2, 2));
+      obj.changed(new TextChangeEvent('hi\u00a0', 3, 3));
+      obj.changed(new TextChangeEvent('hi t', 4, 4));
+      assertEqualStringArrays(['h', 'i', 'hi ', 't'], tts.get());
+    });
+AX_TEST_F('ChromeVoxEditableTextUnitTest', 'DoesNotSpeakDeleted', function() {
   var tts = new TestTts();
   var obj = createEditableText('Hello', 0, 0, false, tts);
   obj.multiline = true;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/phonetic_data_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/phonetic_data_test.js
index 6fcc209..c5b8be3 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/phonetic_data_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/phonetic_data_test.js
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js']);
 
 /**
  * Test fixture for PhoneticData.
  */
-ChromeVoxPhoneticDataTest = class extends AccessibilityTestBase {
+ChromeVoxPhoneticDataTest = class extends ChromeVoxE2ETest {
   /** @override */
   async setUpDeferred() {
     await super.setUpDeferred();
@@ -20,13 +20,6 @@
   }
 };
 
-/** @override */
-ChromeVoxPhoneticDataTest.prototype.extraLibraries = [
-  '../../common/testing/assert_additions.js',
-  '../testing/fake_dom.js',
-  '../third_party/tamachiyomi/ja_phonetic_data.js',
-];
-
 /**
  * This is only for test. Note that reading is different from production.
  * This map is not always used for determining phonetic readings. For example,
@@ -61,7 +54,7 @@
 
 // TODO(crbug/1195393): Polish phonetic readings so that users can disambiguate
 // more precisely.
-TEST_F('ChromeVoxPhoneticDataTest', 'forCharacterJa', function() {
+AX_TEST_F('ChromeVoxPhoneticDataTest', 'forCharacterJa', function() {
   assertEquals('ヒラガナ アサヒ ノ ア', PhoneticData.forCharacter('あ', 'ja'));
   assertEquals('カタカナ アサヒ ノ ア', PhoneticData.forCharacter('ア', 'ja'));
   assertEquals(
@@ -88,7 +81,7 @@
   assertEquals('アジア ノ ア', PhoneticData.forCharacter('亜', 'ja'));
 });
 
-TEST_F('ChromeVoxPhoneticDataTest', 'forTextJaSingleCharacter', function() {
+AX_TEST_F('ChromeVoxPhoneticDataTest', 'forTextJaSingleCharacter', function() {
   assertEquals('あ', PhoneticData.forText('あ', 'ja'));
   assertEquals('カタカナ ア', PhoneticData.forText('ア', 'ja'));
   assertEquals('あ', PhoneticData.forText('ぁ', 'ja'));
@@ -108,7 +101,7 @@
   assertEquals('アジア ノ ア', PhoneticData.forText('亜', 'ja'));
 });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest', 'forTextJaPairCharacters_EndWithHiragana',
     function() {
       assertEquals('ああ', PhoneticData.forText('ああ', 'ja'));
@@ -116,7 +109,7 @@
       assertEquals('オオモジ A あ', PhoneticData.forText('Aあ', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest', 'forTextJaPairCharacters_EndWithKatakana',
     function() {
       assertEquals('カタカナ アア', PhoneticData.forText('アア', 'ja'));
@@ -125,7 +118,7 @@
       assertEquals('あ カタカナ ア', PhoneticData.forText('あア', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHiraganaSmallLetter', function() {
       assertEquals('あぁ', PhoneticData.forText('ぁぁ', 'ja'));
@@ -133,7 +126,7 @@
       assertEquals('アジア ノ ア あ', PhoneticData.forText('亜ぁ', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithKatakanaSmallLetter', function() {
       assertEquals(
@@ -143,7 +136,7 @@
           'あ カタカナ チイサイ ア', PhoneticData.forText('あァ', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthKatakana', function() {
       assertEquals('ハンカク アア', PhoneticData.forText('アア', 'ja'));
@@ -151,7 +144,7 @@
       assertEquals('あ ハンカク ア', PhoneticData.forText('あア', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthKatakanaSmallLetter', function() {
       assertEquals('ハンカク チイサイ アァ', PhoneticData.forText('ァァ', 'ja'));
@@ -159,7 +152,7 @@
       assertEquals('あ ハンカク チイサイ ア', PhoneticData.forText('あァ', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthAlphabetUpper', function() {
       assertEquals('オオモジ AA', PhoneticData.forText('AA', 'ja'));
@@ -171,7 +164,7 @@
       assertEquals('あ オオモジ A', PhoneticData.forText('あA', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthAlphabetLower', function() {
       assertEquals('aa', PhoneticData.forText('aa', 'ja'));
@@ -180,7 +173,7 @@
           'ゼンカクオオモジ A ハンカク a', PhoneticData.forText('Aa', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthNumeric', function() {
       assertEquals('11', PhoneticData.forText('11', 'ja'));
@@ -189,7 +182,7 @@
           'ゼンカクオオモジ A ハンカク 1', PhoneticData.forText('A1', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithHalfWidthSymbol', function() {
       assertEquals(
@@ -200,7 +193,7 @@
           PhoneticData.forText('A@', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthAlphabetUpper', function() {
       assertEquals('ゼンカクオオモジ AA', PhoneticData.forText('AA', 'ja'));
@@ -210,7 +203,7 @@
           'あ ゼンカクオオモジ A', PhoneticData.forText('あA', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthAlphabetLower', function() {
       assertEquals('ゼンカク aa', PhoneticData.forText('aa', 'ja'));
@@ -219,7 +212,7 @@
       assertEquals('あ ゼンカク a', PhoneticData.forText('あa', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthNumeric', function() {
       assertEquals('ゼンカク 11', PhoneticData.forText('11', 'ja'));
@@ -228,7 +221,7 @@
       assertEquals('あ ゼンカク 1', PhoneticData.forText('あ1', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthSymbol', function() {
       assertEquals(
@@ -241,7 +234,7 @@
           'あ ゼンカク アットマーク', PhoneticData.forText('あ@', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthGreekUpper', function() {
       assertEquals(
@@ -251,7 +244,7 @@
           'あ オオモジ ギリシャ アルファ', PhoneticData.forText('あΑ', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest',
     'forTextJaPairCharacters_EndWithFullWidthGreekLower', function() {
       assertEquals(
@@ -263,14 +256,14 @@
       assertEquals('あ ギリシャ アルファ', PhoneticData.forText('あα', 'ja'));
     });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxPhoneticDataTest', 'forTextJaPairCharacters_EndWithOther',
     function() {
       assertEquals(
           'アジア ノ ア アジア ノ ア', PhoneticData.forText('亜亜', 'ja'));
     });
 
-TEST_F('ChromeVoxPhoneticDataTest', 'forTextJaLongSound', function() {
+AX_TEST_F('ChromeVoxPhoneticDataTest', 'forTextJaLongSound', function() {
   assertEquals('あー', PhoneticData.forText('あー', 'ja'));
   assertEquals('カタカナ アー', PhoneticData.forText('アー', 'ja'));
   assertEquals('あー', PhoneticData.forText('ぁー', 'ja'));
@@ -281,7 +274,7 @@
   assertEquals('アジア ノ ア チョウオン', PhoneticData.forText('亜ー', 'ja'));
 });
 
-TEST_F('ChromeVoxPhoneticDataTest', 'forTextSampleSentences', function() {
+AX_TEST_F('ChromeVoxPhoneticDataTest', 'forTextSampleSentences', function() {
   assertEquals(
       'コンゲツノコン ニチヨウビノニチ は テンキヨホウノテン クウキノキ です マル',
       PhoneticData.forText('今日は天気です。', 'ja'));
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/key_sequence_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/key_sequence_test.js
index 84fce1a..bceff2a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/key_sequence_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/key_sequence_test.js
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js']);
 
 /**
  * Test fixture.
  */
-ChromeVoxKeySequenceUnitTest = class extends AccessibilityTestBase {
+ChromeVoxKeySequenceUnitTest = class extends ChromeVoxE2ETest {
   /**
    * Create mock event object.
    * @param {number} keyCode The event key code (i.e. 13 for Enter).
@@ -154,33 +154,28 @@
   }
 };
 
-/** @override */
-ChromeVoxKeySequenceUnitTest.prototype.extraLibraries = [
-  '../../common/testing/assert_additions.js',
-  '../testing/fake_dom.js',
-];
+AX_TEST_F(
+    'ChromeVoxKeySequenceUnitTest', 'SimpleSequenceNoModifier', function() {
+      const downKey = new KeySequence(this.downArrowEvent, false);
 
-TEST_F('ChromeVoxKeySequenceUnitTest', 'SimpleSequenceNoModifier', function() {
-  const downKey = new KeySequence(this.downArrowEvent, false);
+      assertEqualsJSON([KeyCode.DOWN], downKey.keys.keyCode);
+      assertFalse(downKey.stickyMode);
+      assertFalse(downKey.prefixKey);
+      assertFalse(downKey.cvoxModifier);
 
-  assertEqualsJSON([KeyCode.DOWN], downKey.keys.keyCode);
-  assertFalse(downKey.stickyMode);
-  assertFalse(downKey.prefixKey);
-  assertFalse(downKey.cvoxModifier);
+      assertEqualsJSON([false], downKey.keys.altGraphKey);
+      assertEqualsJSON([false], downKey.keys.altKey);
+      assertEqualsJSON([false], downKey.keys.ctrlKey);
+      assertEqualsJSON([false], downKey.keys.metaKey);
+      assertEqualsJSON([false], downKey.keys.searchKeyHeld);
+      assertEqualsJSON([false], downKey.keys.shiftKey);
 
-  assertEqualsJSON([false], downKey.keys.altGraphKey);
-  assertEqualsJSON([false], downKey.keys.altKey);
-  assertEqualsJSON([false], downKey.keys.ctrlKey);
-  assertEqualsJSON([false], downKey.keys.metaKey);
-  assertEqualsJSON([false], downKey.keys.searchKeyHeld);
-  assertEqualsJSON([false], downKey.keys.shiftKey);
-
-  assertEquals(1, downKey.length());
-});
+      assertEquals(1, downKey.length());
+    });
 
 
 /** Test another key sequence, this time with the modifier */
-TEST_F(
+AX_TEST_F(
     'ChromeVoxKeySequenceUnitTest', 'SimpleSequenceWithModifier', function() {
       const downKey = new KeySequence(this.downArrowEvent, true);
 
@@ -201,7 +196,7 @@
 
 
 /** Test a key sequence that includes the modifier */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'ModifiedSequence', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'ModifiedSequence', function() {
   const cvoxDownKey = new KeySequence(this.altDownArrowEvent, true);
 
   assertEqualsJSON([KeyCode.DOWN], cvoxDownKey.keys.keyCode);
@@ -225,7 +220,7 @@
  * These should be equal because Ctrl should still function even with
  * sticky mode on.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'StickyEquality', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'StickyEquality', function() {
   const ctrlKey = new KeySequence(this.ctrlEvent, false);
   const ctrlSticky = new KeySequence(this.ctrlStickyEvent, false);
 
@@ -238,7 +233,7 @@
  * modifier.
  * These should not be equal because they do not have the same modifiers.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'ShiftEquality', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'ShiftEquality', function() {
   const aKey = new KeySequence(this.aEvent, false);
   const shiftA = new KeySequence(this.shiftAEvent, false);
 
@@ -251,7 +246,7 @@
  * on, 'a' with prefix key, and 'a' with ChromeVox modifier held down. These
  * should all be equal to each other.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'FourWayEquality', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'FourWayEquality', function() {
   const commandSequence = new KeySequence(this.aEvent, true);
   const stickySequence = new KeySequence(this.aEventSticky, false);
   const prefixSequence = new KeySequence(this.aEventPrefix, false);
@@ -281,7 +276,7 @@
  * modifier specified vs. 'a' key with ChromeVox modifier held down.
  * These should all be equal to each other..
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'ShiftPrefixEquality', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'ShiftPrefixEquality', function() {
   const shiftAWithModifier = new KeySequence(this.shiftAEvent, true);
   const shiftAWithPrefix = new KeySequence(this.shiftAPrefixEvent, false);
   const shiftASticky = new KeySequence(this.shiftAStickyEvent, false);
@@ -309,7 +304,7 @@
  * Test inequality - 'a' with modifier key vs. 'a' without modifier key.
  * These should not be equal.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'Inequality', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'Inequality', function() {
   const aNoModifier = new KeySequence(this.aEvent, false);
   const aWithModifier = new KeySequence(this.aEvent, true);
 
@@ -321,7 +316,7 @@
 /**
  * Test equality - adding an additional key onto a sequence.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'CvoxCtrl', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'CvoxCtrl', function() {
   const cvoxCtrlSequence = new KeySequence(this.ctrlEvent, true);
   assertTrue(cvoxCtrlSequence.addKeyEvent(this.rightArrowEvent));
 
@@ -350,7 +345,7 @@
 /**
  * Test for inequality - key sequences in different orders.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'DifferentSequences', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'DifferentSequences', function() {
   const cvoxBSequence = new KeySequence(this.bEvent, true);
   assertTrue(cvoxBSequence.addKeyEvent(this.cEvent));
 
@@ -366,7 +361,7 @@
  * Tests modifiers (ctrl, alt, etc) - if two sequences have different modifiers
  * held down then they aren't equal.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'MoreModifiers', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'MoreModifiers', function() {
   const ctrlASequence = new KeySequence(this.ctrlAEvent, false);
   const ctrlModifierKeyASequence = new KeySequence(this.ctrlAEvent, true);
 
@@ -384,7 +379,7 @@
  * Tests modifier (ctrl, alt, etc) order - if two sequences have the same
  * modifiers but held down in a different order then they aren't equal.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'ModifierOrder', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'ModifierOrder', function() {
   const ctrlShiftSequence = new KeySequence(this.ctrlShiftEvent, false);
   const shiftCtrlSequence = new KeySequence(this.shiftCtrlEvent, true);
 
@@ -395,7 +390,7 @@
 /**
  * Tests converting from a string to a KeySequence object.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'FromStr', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'FromStr', function() {
   const ctrlString = KeySequence.fromStr('Ctrl');
   assertEqualsJSON(ctrlString.keys.ctrlKey, [true]);
   assertEqualsJSON(ctrlString.keys.keyCode, [KeyCode.CONTROL]);
@@ -432,7 +427,7 @@
 /**
  * Tests converting from a JSON string to a KeySequence object.
  */
-TEST_F('ChromeVoxKeySequenceUnitTest', 'Deserialize', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'Deserialize', function() {
   const forwardSequence = KeySequence.deserialize({
     'cvoxModifier': true,
     'stickyMode': false,
@@ -468,7 +463,7 @@
   assertEqualsJSON(ctrlSequence.keys.keyCode, [KeyCode.CONTROL]);
 });
 
-TEST_F(
+AX_TEST_F(
     'ChromeVoxKeySequenceUnitTest', 'DeserializeAltShiftCvoxMod', function() {
       KeySequence.modKeyStr = 'Alt+Shift';
 
@@ -488,31 +483,34 @@
       assertTrue(prevHeadingSeq.keys.shiftKey[0]);
     });
 
-TEST_F('ChromeVoxKeySequenceUnitTest', 'DeserializeSearchCvoxMod', function() {
-  // Test the case when we do want to strip modifiers when deserializing. This
-  // is important when the key sequence in the key map and the key sequence at
-  // runtime both contain the bare cvox modifier as a key code such as in the
-  // case of the Search sticky key and Search cvox modifier. Stripping happens
-  // by default for key events at runtime.
-  KeySequence.modKeyStr = 'Search';
+AX_TEST_F(
+    'ChromeVoxKeySequenceUnitTest', 'DeserializeSearchCvoxMod', function() {
+      // Test the case when we do want to strip modifiers when deserializing.
+      // This is important when the key sequence in the key map and the key
+      // sequence at runtime both contain the bare cvox modifier as a key code
+      // such as in the case of the Search sticky key and Search cvox modifier.
+      // Stripping happens by default for key events at runtime.
+      KeySequence.modKeyStr = 'Search';
 
-  // First, assert that unstripped seqs imply various modifier fields get set.
-  let stickySeq = KeySequence.deserialize({keys: {keyCode: [KeyCode.SEARCH]}});
-  assertTrue(stickySeq.cvoxModifier);
-  assertTrue(stickySeq.keys.metaKey[0]);
-  assertTrue(stickySeq.keys.searchKeyHeld[0]);
+      // First, assert that unstripped seqs imply various modifier fields get
+      // set.
+      let stickySeq =
+          KeySequence.deserialize({keys: {keyCode: [KeyCode.SEARCH]}});
+      assertTrue(stickySeq.cvoxModifier);
+      assertTrue(stickySeq.keys.metaKey[0]);
+      assertTrue(stickySeq.keys.searchKeyHeld[0]);
 
-  // Next, assert that stripping causes those modifiers to get unset. This is
-  // desirable at runtime so that we can match against the stripped runtime key
-  // seqs.
-  stickySeq = KeySequence.deserialize(
-      {'skipStripping': false, keys: {keyCode: [KeyCode.SEARCH]}});
-  assertTrue(stickySeq.cvoxModifier);
-  assertFalse(stickySeq.keys.metaKey[0]);
-  assertFalse(stickySeq.keys.searchKeyHeld[0]);
-});
+      // Next, assert that stripping causes those modifiers to get unset. This
+      // is desirable at runtime so that we can match against the stripped
+      // runtime key seqs.
+      stickySeq = KeySequence.deserialize(
+          {'skipStripping': false, keys: {keyCode: [KeyCode.SEARCH]}});
+      assertTrue(stickySeq.cvoxModifier);
+      assertFalse(stickySeq.keys.metaKey[0]);
+      assertFalse(stickySeq.keys.searchKeyHeld[0]);
+    });
 
-TEST_F('ChromeVoxKeySequenceUnitTest', 'RequireStickyMode', function() {
+AX_TEST_F('ChromeVoxKeySequenceUnitTest', 'RequireStickyMode', function() {
   const oneFromMap = KeySequence.deserialize(
       {requireStickyMode: true, keys: {keyCode: [KeyCode.ONE]}});
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable_test.js
index 8455a9b..b0adced 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/common/spannable_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['../testing/chromevox_e2e_test_base.js']);
 
 UnserializableSpan = function() {};
 
@@ -57,7 +57,7 @@
 /**
  * Test fixture.
  */
-ChromeVoxSpannableUnitTest = class extends AccessibilityTestBase {
+ChromeVoxSpannableUnitTest = class extends ChromeVoxE2ETest {
   /** @override */
   setUp() {
     super.setUp();
@@ -80,28 +80,20 @@
   }
 };
 
-
-/** @override */
-ChromeVoxSpannableUnitTest.prototype.extraLibraries = [
-  '../../common/testing/assert_additions.js',
-  '../testing/fake_dom.js',
-];
-
-
-TEST_F('ChromeVoxSpannableUnitTest', 'ToStringUnannotated', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'ToStringUnannotated', function() {
   assertEquals('', new Spannable().toString());
   assertEquals('hello world', new Spannable('hello world').toString());
 });
 
 /** Tests that toString works correctly on annotated strings. */
-TEST_F('ChromeVoxSpannableUnitTest', 'ToStringAnnotated', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'ToStringAnnotated', function() {
   const spannable = new Spannable('Hello Google');
   spannable.setSpan('http://www.google.com/', 6, 12);
   assertEquals('Hello Google', spannable.toString());
 });
 
 /** Tests the length calculation. */
-TEST_F('ChromeVoxSpannableUnitTest', 'LengthProperty', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'LengthProperty', function() {
   const spannable = new Spannable('Hello');
   spannable.setSpan({}, 0, 3);
   assertEquals(5, spannable.length);
@@ -112,7 +104,7 @@
 });
 
 /** Tests that a span can be added and retrieved at the beginning. */
-TEST_F('ChromeVoxSpannableUnitTest', 'SpanBeginning', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SpanBeginning', function() {
   const annotation = {};
   const spannable = new Spannable('Hello world');
   spannable.setSpan(annotation, 0, 5);
@@ -124,7 +116,7 @@
 });
 
 /** Tests that a span can be added and retrieved at the beginning. */
-TEST_F('ChromeVoxSpannableUnitTest', 'SpanEnd', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SpanEnd', function() {
   const annotation = {};
   const spannable = new Spannable('Hello world');
   spannable.setSpan(annotation, 6, 11);
@@ -136,7 +128,7 @@
 });
 
 /** Tests that a zero-length span is not retrieved. */
-TEST_F('ChromeVoxSpannableUnitTest', 'SpanZeroLength', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SpanZeroLength', function() {
   const annotation = {};
   const spannable = new Spannable('Hello world');
   spannable.setSpan(annotation, 3, 3);
@@ -147,7 +139,7 @@
 });
 
 /** Tests that a removed span is not returned. */
-TEST_F('ChromeVoxSpannableUnitTest', 'RemoveSpan', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'RemoveSpan', function() {
   const annotation = {};
   const spannable = new Spannable('Hello world');
   spannable.setSpan(annotation, 0, 3);
@@ -158,7 +150,7 @@
 });
 
 /** Tests that adding a span in one place removes it from another. */
-TEST_F('ChromeVoxSpannableUnitTest', 'SetSpanMoves', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SetSpanMoves', function() {
   const annotation = {};
   const spannable = new Spannable('Hello world');
   spannable.setSpan(annotation, 0, 3);
@@ -170,7 +162,7 @@
 });
 
 /** Tests that setSpan objects to out-of-range arguments. */
-TEST_F('ChromeVoxSpannableUnitTest', 'SetSpanRangeError', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SetSpanRangeError', function() {
   const spannable = new Spannable('Hello world');
 
   // Start index out of range.
@@ -193,7 +185,7 @@
  * Tests that multiple spans can be retrieved at one point.
  * The first one added which applies should be returned by getSpan.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'MultipleSpans', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'MultipleSpans', function() {
   const annotation1 = {number: 1};
   const annotation2 = {number: 2};
   assertNotSame(annotation1, annotation2);
@@ -209,7 +201,7 @@
 });
 
 /** Tests that appending appends the strings. */
-TEST_F('ChromeVoxSpannableUnitTest', 'AppendToString', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'AppendToString', function() {
   const spannable = new Spannable('Google');
   assertEquals('Google', spannable.toString());
   spannable.append(' Chrome');
@@ -221,7 +213,7 @@
 /**
  * Tests that appending Spannables combines annotations.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'AppendAnnotations', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'AppendAnnotations', function() {
   const annotation1 = {number: 1};
   const annotation2 = {number: 2};
   assertNotSame(annotation1, annotation2);
@@ -237,19 +229,20 @@
 /**
  * Tests that a span's bounds can be retrieved.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndAndLength', function() {
-  const annotation = {};
-  const spannable = new Spannable('potato wedges');
-  spannable.setSpan(annotation, 8, 12);
-  assertEquals(8, spannable.getSpanStart(annotation));
-  assertEquals(12, spannable.getSpanEnd(annotation));
-  assertEquals(4, spannable.getSpanLength(annotation));
-});
+AX_TEST_F(
+    'ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndAndLength', function() {
+      const annotation = {};
+      const spannable = new Spannable('potato wedges');
+      spannable.setSpan(annotation, 8, 12);
+      assertEquals(8, spannable.getSpanStart(annotation));
+      assertEquals(12, spannable.getSpanEnd(annotation));
+      assertEquals(4, spannable.getSpanLength(annotation));
+    });
 
 /**
  * Tests that an absent span's bounds are reported correctly.
  */
-TEST_F(
+AX_TEST_F(
     'ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndAndLengthAbsent',
     function() {
       const annotation = {};
@@ -260,7 +253,7 @@
 /**
  * Test that a zero length span can still be found.
  */
-TEST_F(
+AX_TEST_F(
     'ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndAndLengthZeroLength',
     function() {
       const annotation = {};
@@ -275,21 +268,22 @@
  * Tests that == (but not ===) objects are treated distinctly when getting
  * span bounds.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndEquality', function() {
-  // Note that 0 == '' and '' == 0 in JavaScript.
-  const spannable = new Spannable('wat');
-  spannable.setSpan(0, 0, 0);
-  spannable.setSpan('', 1, 3);
-  assertEquals(0, spannable.getSpanStart(0));
-  assertEquals(0, spannable.getSpanEnd(0));
-  assertEquals(1, spannable.getSpanStart(''));
-  assertEquals(3, spannable.getSpanEnd(''));
-});
+AX_TEST_F(
+    'ChromeVoxSpannableUnitTest', 'GetSpanStartAndEndEquality', function() {
+      // Note that 0 == '' and '' == 0 in JavaScript.
+      const spannable = new Spannable('wat');
+      spannable.setSpan(0, 0, 0);
+      spannable.setSpan('', 1, 3);
+      assertEquals(0, spannable.getSpanStart(0));
+      assertEquals(0, spannable.getSpanEnd(0));
+      assertEquals(1, spannable.getSpanStart(''));
+      assertEquals(3, spannable.getSpanEnd(''));
+    });
 
 /**
  * Tests that substrings have the correct character sequence.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'Substring', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'Substring', function() {
   const assertSubstringResult = function(expected, initial, start, opt_end) {
     const spannable = new Spannable(initial);
     const substring = spannable.substring(start, opt_end);
@@ -304,7 +298,7 @@
 /**
  * Tests that substring arguments are validated properly.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'SubstringRangeError', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SubstringRangeError', function() {
   const assertRangeError = function(initial, start, opt_end) {
     const spannable = new Spannable(initial);
     assertException('expected range error', function() {
@@ -319,7 +313,7 @@
 /**
  * Tests that spans in the substring range are preserved.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansIncluded', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansIncluded', function() {
   const assertSpanIncluded = function(
       expectedSpanStart, expectedSpanEnd, initial, initialSpanStart,
       initialSpanEnd, start, opt_end) {
@@ -353,7 +347,7 @@
  * Tests that spans outside the range are omitted.
  * It's fine to keep zero-length spans at the ends, though.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansExcluded', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansExcluded', function() {
   const assertSpanExcluded = function(
       initial, spanStart, spanEnd, start, opt_end) {
     const annotation = {};
@@ -371,7 +365,7 @@
 /**
  * Tests that spans which cross the boundary are clipped.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansClipped', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'SubstringSpansClipped', function() {
   const assertSpanIncluded = function(
       expectedSpanStart, expectedSpanEnd, initial, initialSpanStart,
       initialSpanEnd, start, opt_end) {
@@ -394,7 +388,7 @@
 /**
  * Tests that whitespace is trimmed.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'Trim', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'Trim', function() {
   const assertTrimResult = function(expected, initial) {
     assertEquals(expected, new Spannable(initial).trim().toString());
   };
@@ -409,7 +403,7 @@
 /**
  * Tests that trim keeps, drops and clips spans.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'TrimSpans', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'TrimSpans', function() {
   const spannable = new Spannable(' \t Kennedy\n');
   spannable.setSpan('tab', 1, 2);
   spannable.setSpan('jfk', 3, 10);
@@ -425,7 +419,7 @@
 /**
  * Tests that when a string is all whitespace, we trim off the *end*.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'TrimAllWhitespace', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'TrimAllWhitespace', function() {
   const spannable = new Spannable('    ');
   spannable.setSpan('cursor 1', 0, 0);
   spannable.setSpan('cursor 2', 2, 2);
@@ -438,7 +432,7 @@
 /**
  * Tests finding a span which is an instance of a given class.
  */
-TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanInstanceOf', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanInstanceOf', function() {
   function ExampleConstructorBase() {}
   function ExampleConstructor1() {}
   function ExampleConstructor2() {}
@@ -458,7 +452,7 @@
 });
 
 /** Tests trimming only left or right. */
-TEST_F('ChromeVoxSpannableUnitTest', 'TrimLeftOrRight', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'TrimLeftOrRight', function() {
   const spannable = new Spannable('    ');
   spannable.setSpan('cursor 1', 0, 0);
   spannable.setSpan('cursor 2', 2, 2);
@@ -494,7 +488,7 @@
   assertEquals(0, trimmed3.getSpanEnd('cursor 2'));
 });
 
-TEST_F('ChromeVoxSpannableUnitTest', 'Serialize', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'Serialize', function() {
   const fresh = new Spannable('text');
   const freshStatelessSerializable = new StatelessSerializableSpan();
   const freshNonStatelessSerializable = new NonStatelessSerializableSpan(14);
@@ -518,7 +512,7 @@
       freshNonStatelessSerializable, thawnNonStatelessSerializable);
 });
 
-TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanIntervals', function() {
+AX_TEST_F('ChromeVoxSpannableUnitTest', 'GetSpanIntervals', function() {
   function Foo() {}
   function Bar() {}
   const ms = new MultiSpannable('f12b45f78b01');
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
index 01ab883..3c99b0a 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/testing/mock_feedback_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-GEN_INCLUDE(['../../common/testing/accessibility_test_base.js']);
+GEN_INCLUDE(['chromevox_e2e_test_base.js']);
 
 function speak(text, opt_properties) {
   ChromeVox.tts.speak(text, 0, opt_properties);
@@ -21,7 +21,7 @@
 /**
  * Test fixture.
  */
-MockFeedbackUnitTest = class extends AccessibilityTestBase {
+MockFeedbackUnitTest = class extends ChromeVoxE2ETest {
   constructor() {
     super();
     this.expectedCalls = [];
@@ -41,13 +41,7 @@
   }
 };
 
-MockFeedbackUnitTest.prototype.extraLibraries = [
-  '../../common/testing/assert_additions.js',
-  '../testing/fake_dom.js',  // Must come before other files
-  'mock_feedback.js',
-];
-
-TEST_F('MockFeedbackUnitTest', 'speechAndCallbacks', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'speechAndCallbacks', function() {
   let afterThirdStringCalled = false;
   let spruiousStringEndCallbackCalled = false;
   let finishCalled = false;
@@ -81,7 +75,7 @@
   assertTrue(finishCalled);
 });
 
-TEST_F('MockFeedbackUnitTest', 'startAndEndCallbacks', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'startAndEndCallbacks', function() {
   let onlyStartCallbackCalled = false;
   let onlyEndCallbackCalled = false;
   let bothCallbacksStartCalled = false;
@@ -122,7 +116,7 @@
   assertTrue(bothCallbacksEndCalled);
 });
 
-TEST_F('MockFeedbackUnitTest', 'SpeechAndBraille', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'SpeechAndBraille', function() {
   let secondCallbackCalled = false;
   let finishCalled = false;
   const mock = new MockFeedback(function() {
@@ -149,7 +143,7 @@
   assertTrue(finishCalled);
 });
 
-TEST_F('MockFeedbackUnitTest', 'expectWithRegex', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'expectWithRegex', function() {
   let done = false;
   const mock = new MockFeedback();
   mock.install();
@@ -164,7 +158,7 @@
   assertTrue(done);
 });
 
-TEST_F('MockFeedbackUnitTest', 'expectAfterReplayThrows', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'expectAfterReplayThrows', function() {
   const mock = new MockFeedback();
   mock.replay();
   assertException('', function() {
@@ -172,7 +166,7 @@
   }, 'AssertionError');
 });
 
-TEST_F('MockFeedbackUnitTest', 'NoMatchDoesNotFinish', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'NoMatchDoesNotFinish', function() {
   let firstCallbackCalled = false;
   const mock = new MockFeedback(function() {
     throw Error('Should not be called');
@@ -191,7 +185,7 @@
   assertTrue(firstCallbackCalled);
 });
 
-TEST_F('MockFeedbackUnitTest', 'SpeechAndEarcons', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'SpeechAndEarcons', function() {
   let finishCalled = false;
   const mock = new MockFeedback(function() {
     finishCalled = true;
@@ -221,7 +215,7 @@
   assertTrue(finishCalled);
 });
 
-TEST_F('MockFeedbackUnitTest', 'SpeechWithLanguage', function() {
+AX_TEST_F('MockFeedbackUnitTest', 'SpeechWithLanguage', function() {
   let finishCalled = false;
   const mock = new MockFeedback(function() {
     finishCalled = true;
diff --git a/chrome/browser/resources/chromeos/accessibility/common/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/common/BUILD.gn
index f250ad6..7356850f 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/common/BUILD.gn
@@ -15,6 +15,7 @@
 
 accessibility_common_out_dir =
     "$root_out_dir/resources/chromeos/accessibility/common"
+tsc_preprocess_folder = "$root_gen_dir/chrome/browser/resources/chromeos/accessibility/tsc_preprocessed"
 tsc_out_dir = "$target_gen_dir/tsc"
 
 group("build") {
@@ -24,6 +25,29 @@
   ]
 }
 
+js_sources = [
+  "async_util.js",
+  "automation_predicate.js",
+  "browser_util.js",
+  "constants.js",
+  "cursors/cursor.js",
+  "cursors/range.js",
+  "cursors/recovery_strategy.js",
+  "event_generator.js",
+  "event_handler.js",
+  "gdocs_script.js",
+  "key_code.js",
+  "local_storage.js",
+  "node_navigation_utils.js",
+  "node_utils.js",
+  "rect_util.js",
+  "repeated_event_handler.js",
+  "repeated_tree_change_handler.js",
+  "sentence_utils.js",
+  "testing/accessibility_test_base.js",
+  "testing/test_node_generator.js",
+]
+
 # Add typescript files to compile here.
 ts_modules = [
   "array_util.ts",
@@ -38,55 +62,50 @@
   "string_util.ts",
 ]
 
+ts_definitions = [
+  "../definitions/automation.d.ts",
+  "../definitions/command_line_private.d.ts",
+  "../definitions/extensions.d.ts",
+  "../definitions/extension_types.d.ts",
+  "../definitions/i18n.d.ts",
+  "../definitions/runtime.d.ts",
+  "../definitions/settings_private_mv2.d.ts",
+  "../definitions/tabs.d.ts",
+  "../definitions/tts.d.ts",
+  "//tools/typescript/definitions/windows.d.ts",
+]
+
+# Generated TS definition files.
+generated_ts_definitions =
+    [ "$tsc_preprocess_folder/definitions/chrome_event.d.ts" ]
+
 ts_library("ts_build") {
-  in_files = ts_modules
+  root_dir = "$tsc_preprocess_folder"
   out_dir = tsc_out_dir
-  definitions = [
-    "../definitions/automation.d.ts",
-    "../definitions/command_line_private.d.ts",
-    "../definitions/extensions.d.ts",
-    "../definitions/extension_types.d.ts",
-    "../definitions/i18n.d.ts",
-    "../definitions/runtime.d.ts",
-    "../definitions/settings_private_mv2.d.ts",
-    "../definitions/tabs.d.ts",
-    "../definitions/tts.d.ts",
-    "//tools/typescript/definitions/windows.d.ts",
-  ]
+  definitions = ts_definitions + generated_ts_definitions
+
+  in_files = []
+  foreach(_ts_file, ts_modules) {
+    in_files += [ "common/" + _ts_file ]
+  }
 
   tsconfig_base = "../tsconfig.base.json"
+
+  extra_deps = [
+    ":preprocess_files_for_ts_build",
+    "..:preprocess_files_for_ts_build",
+  ]
 }
 
 run_jsbundler("copied_files") {
   mode = "copy"
   dest_dir = accessibility_common_out_dir
   deps = [ ":ts_build" ]
-  sources = [
-    "async_util.js",
-    "automation_predicate.js",
-    "browser_util.js",
-    "constants.js",
-    "cursors/cursor.js",
-    "cursors/range.js",
-    "cursors/recovery_strategy.js",
-    "event_generator.js",
-    "event_handler.js",
-    "gdocs_script.js",
-    "key_code.js",
-    "local_storage.js",
-    "node_navigation_utils.js",
-    "node_utils.js",
-    "rect_util.js",
-    "repeated_event_handler.js",
-    "repeated_tree_change_handler.js",
-    "sentence_utils.js",
-    "testing/accessibility_test_base.js",
-    "testing/test_node_generator.js",
-  ]
-  sources += filter_include(get_target_outputs(":ts_build"), [ "*.js" ])
+  sources =
+      js_sources + filter_include(get_target_outputs(":ts_build"), [ "*.js" ])
 
   rewrite_rules = [
-    rebase_path(tsc_out_dir, root_build_dir) + ":",
+    rebase_path("$tsc_out_dir/common", root_build_dir) + ":",
     rebase_path(".", root_build_dir) + ":",
   ]
 }
@@ -160,3 +179,11 @@
   }
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
 }
+
+# Copy all JS and TS sources to a preprocess folder. All generated TS/JS files
+# will also be copied into this folder, which will allow us to support a TS
+# build that uses both checked-in and generated files.
+copy("preprocess_files_for_ts_build") {
+  sources = js_sources + ts_modules
+  outputs = [ "$tsc_preprocess_folder/common/{{source_target_relative}}" ]
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/common/chrome_event_handler.ts b/chrome/browser/resources/chromeos/accessibility/common/chrome_event_handler.ts
index d6bafd5d..151182b 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/chrome_event_handler.ts
+++ b/chrome/browser/resources/chromeos/accessibility/common/chrome_event_handler.ts
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {ChromeEvent} from '../../../../../../tools/typescript/definitions/chrome_event.js';
+// Note: This definition file is generated at build time.
+import {ChromeEvent} from '../definitions/chrome_event.js';
 
 type GenericListener<T extends any[]> = (...args: T) => void;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
index eca39a4..a96cacc 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
@@ -18,7 +18,10 @@
 tsc_out_dir = "$target_gen_dir/tsc"
 
 # TS files to compile.
-ts_modules = [ "commands.ts" ]
+ts_modules = [
+  "commands.ts",
+  "navigator_interfaces.ts",
+]
 
 # JS files needed to compile TS.
 js_deps = []
@@ -100,7 +103,6 @@
     "menu_manager.js",
     "metrics.js",
     "navigator.js",
-    "navigator_interfaces.js",
     "nodes/back_button_node.js",
     "nodes/basic_node.js",
     "nodes/combo_box_node.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.js b/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.js
deleted file mode 100644
index 71db4e3..0000000
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.js
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {constants} from '../common/constants.js';
-
-import {SAChildNode, SANode, SARootNode} from './nodes/switch_access_node.js';
-
-const AutomationNode = chrome.automation.AutomationNode;
-const MenuAction = chrome.accessibilityPrivate.SwitchAccessMenuAction;
-
-/** @abstract */
-export class ItemNavigatorInterface {
-  /**
-   * @param {!SAChildNode} node
-   * @return {boolean}
-   * @abstract
-   */
-  currentGroupHasChild(node) {}
-
-  /**
-   * Enters |this.node_|.
-   * @abstract
-   */
-  enterGroup() {}
-
-  /**
-   * Puts focus on the virtual keyboard, if the current node is a text input.
-   * @abstract
-   */
-  enterKeyboard() {}
-
-  /**
-   * Unconditionally exits the current group.
-   * @abstract
-   */
-  exitGroupUnconditionally() {}
-
-  /**
-   * Exits the specified node, if it is the currently focused group.
-   * @param {?AutomationNode|SANode} node
-   * @abstract
-   */
-  exitIfInGroup(node) {}
-
-  /**
-   * @return {!Promise}
-   * @abstract
-   */
-  async exitKeyboard() {}
-
-  /**
-   * Forces the current node to be |node|.
-   * Should only be called by subclasses of SARootNode and
-   *    only when they are focused.
-   * @param {!SAChildNode} node
-   * @abstract
-   */
-  forceFocusedNode(node) {}
-
-  /**
-   * Returns the current Switch Access tree, for debugging purposes.
-   * @param {boolean} wholeTree Whether to print the whole tree, or just the
-   * current focus.
-   * @return {!SARootNode}
-   * @abstract
-   */
-  getTreeForDebugging(wholeTree) {}
-
-  /**
-   * Jumps to a specific automation node. Maintains the history when navigating.
-   * @param {AutomationNode} automationNode
-   * @abstract
-   */
-  jumpTo(automationNode) {}
-
-  /**
-   * Move to the previous interesting node.
-   * @abstract
-   */
-  moveBackward() {}
-
-  /**
-   * Move to the next interesting node.
-   * @abstract
-   */
-  moveForward() {}
-
-  /**
-   * Tries to move to another node, |node|, but if |node| is a window that's not
-   * in the foreground it will use |getNext| to find the next node to try.
-   * Checks against |startingNode| to ensure we don't get stuck in an infinite
-   * loop.
-   * @param {!SAChildNode} node The node to try to move into.
-   * @param {function(!SAChildNode): !SAChildNode} getNext gets the next node to
-   *     try if we cannot move to |next|. Takes |next| as a parameter.
-   * @param {!SAChildNode} startingNode The first node in the sequence. If we
-   *     loop back to this node, stop trying to move, as there are no other
-   *     nodes we can move to.
-   * @return {!Promise}
-   * @abstract
-   */
-  async tryMoving(node, getNext, startingNode) {}
-
-  /**
-   * Moves to the Switch Access focus up the group stack closest to the ancestor
-   * that hasn't been invalidated.
-   * @abstract
-   */
-  moveToValidNode() {}
-
-  /**
-   * Restarts item scanning from the last point chosen by point scanning.
-   * @abstract
-   */
-  restart() {}
-
-  /**
-   * Restores the suspended group and focus, if there is one.
-   * @abstract
-   */
-  restoreSuspendedGroup() {}
-
-  /**
-   * Saves the current focus and group, and then exits the group.
-   * @abstract
-   */
-  suspendCurrentGroup() {}
-
-  // =============== Getter Methods ==============
-
-  /**
-   * Returns the currently focused node.
-   * @return {!SAChildNode}
-   * @abstract
-   */
-  get currentNode() {}
-
-  /**
-   * Returns the desktop automation node object.
-   * @return {!AutomationNode}
-   * @abstract
-   */
-  get desktopNode() {}
-}
-
-/** @abstract */
-export class PointNavigatorInterface {
-  /**
-   * Returns the current point scan point.
-   * @return {!constants.Point}
-   */
-  get currentPoint() {}
-
-  /** Starts point scanning. */
-  start() {}
-
-  /** Stops point scanning. */
-  stop() {}
-
-  /**
-   * Performs a mouse action at the currentPoint().
-   * @param {MenuAction} action
-   */
-  performMouseAction(action) {}
-}
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.ts b/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.ts
new file mode 100644
index 0000000..d5d6398
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/navigator_interfaces.ts
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {constants} from '../common/constants.js';
+
+import {SAChildNode, SANode, SARootNode} from './nodes/switch_access_node.js';
+
+import MenuAction = chrome.accessibilityPrivate.SwitchAccessMenuAction;
+type AutomationNode = chrome.automation.AutomationNode;
+type Point = constants.Point;
+
+export abstract class ItemNavigatorInterface {
+  abstract currentGroupHasChild(node: SAChildNode): boolean;
+
+  /** Enters |this.node_|. */
+  abstract enterGroup(): void;
+
+  /**
+   * Puts focus on the virtual keyboard, if the current node is a text input.
+   */
+  abstract enterKeyboard(): void;
+
+  /** Unconditionally exits the current group. */
+  abstract exitGroupUnconditionally(): void;
+
+  /** Exits the specified node, if it is the currently focused group. */
+  abstract exitIfInGroup(node: (AutomationNode|SANode|null)): void;
+
+  abstract exitKeyboard(): Promise<void>;
+
+  /**
+   * Forces the current node to be |node|.
+   * Should only be called by subclasses of SARootNode and
+   *    only when they are focused.
+   */
+  abstract forceFocusedNode(node: SAChildNode): void;
+
+  /**
+   * Returns the current Switch Access tree, for debugging purposes.
+   * @param wholeTree Whether to print the whole tree, or just the
+   * current focus.
+   */
+  abstract getTreeForDebugging(wholeTree: boolean): SARootNode;
+
+  /**
+   * Jumps to a specific automation node. Maintains the history when
+   * navigating.
+   */
+  abstract jumpTo(automationNode: AutomationNode): void;
+
+  /** Move to the previous interesting node. */
+  abstract moveBackward(): void;
+
+  /** Move to the next interesting node. */
+  abstract moveForward(): void;
+
+  /**
+   * Tries to move to another node, |node|, but if |node| is a window that's not
+   * in the foreground it will use |getNext| to find the next node to try.
+   * Checks against |startingNode| to ensure we don't get stuck in an infinite
+   * loop.
+   * @param node The node to try to move into.
+   * @param getNext gets the next node to
+   *     try if we cannot move to |next|. Takes |next| as a parameter.
+   * @param startingNode The first node in the sequence. If we
+   *     loop back to this node, stop trying to move, as there are no other
+   *     nodes we can move to.
+   */
+  abstract tryMoving(
+      _node: SAChildNode, _getNext: (node: SAChildNode) => SAChildNode,
+      _startingNode: SAChildNode): Promise<void>;
+
+  /**
+   * Moves to the Switch Access focus up the group stack closest to the ancestor
+   * that hasn't been invalidated.
+   */
+  abstract moveToValidNode(): void;
+
+  /** Restarts item scanning from the last point chosen by point scanning. */
+  abstract restart(): void;
+
+  /** Restores the suspended group and focus, if there is one. */
+  abstract restoreSuspendedGroup(): void;
+
+  /** Saves the current focus and group, and then exits the group. */
+  abstract suspendCurrentGroup(): void;
+
+  // =============== Getter Methods ==============
+
+  /** Returns the currently focused node. */
+  abstract get currentNode(): SAChildNode;
+
+  /** Returns the desktop automation node object. */
+  abstract get desktopNode(): AutomationNode;
+}
+
+export abstract class PointNavigatorInterface {
+  /** Returns the current point scan point. */
+  abstract get currentPoint(): Point;
+
+  /** Starts point scanning. */
+  abstract start(): void;
+
+  /** Stops point scanning. */
+  abstract stop(): void;
+
+  /** Performs a mouse action at the currentPoint(). */
+  abstract performMouseAction(action: MenuAction): void;
+}
diff --git a/chrome/browser/resources/chromeos/internet_config_dialog/BUILD.gn b/chrome/browser/resources/chromeos/internet_config_dialog/BUILD.gn
index a656ae8..b2ea9856 100644
--- a/chrome/browser/resources/chromeos/internet_config_dialog/BUILD.gn
+++ b/chrome/browser/resources/chromeos/internet_config_dialog/BUILD.gn
@@ -2,109 +2,36 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//chrome/common/features.gni")
-import("//third_party/closure_compiler/compile_js.gni")
-import("//tools/grit/grit_rule.gni")
-import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/polymer/html_to_js.gni")
-import("//ui/webui/resources/tools/generate_grd.gni")
-import("//ui/webui/resources/tools/optimize_webui.gni")
+import("//ui/webui/resources/tools/build_webui.gni")
 
-preprocess_folder = "preprocessed"
-preprocess_gen_manifest = "preprocessed_gen_manifest.json"
+build_webui("build") {
+  static_files = [ "internet_config_dialog_container.html" ]
 
-if (optimize_webui) {
-  build_manifest = "build_manifest.json"
+  # Files holding a Polymer element definition AND have an equivalent .html file.
+  web_component_files = [ "internet_config_dialog.ts" ]
 
-  optimize_webui("build") {
-    host = "internet-config-dialog"
-    js_module_in_files = [ "internet_config_dialog.js" ]
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
-    out_manifest = "$target_gen_dir/$build_manifest"
-    excludes = [
+  ts_definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+
+  ts_deps = [
+    "//ash/webui/common/resources:build_ts",
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/cr_components/color_change_listener:build_ts",
+    "//ui/webui/resources/cr_elements:build_ts",
+    "//ui/webui/resources/js:build_ts",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
+
+  optimize = optimize_webui
+  if (optimize) {
+    optimize_webui_host = "internet-config-dialog"
+    optimize_webui_in_files = [ "internet_config_dialog.js" ]
+    optimize_webui_excludes = [
       "chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js",
       "chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js",
       "chrome://resources/mojo/services/network/public/mojom/ip_address.mojom-webui.js",
       "chrome://resources/ash/common/hotspot/cros_hotspot_config.mojom-webui.js",
     ]
-
-    deps = [
-      ":preprocess_generated",
-      "//ash/webui/common/resources:css_wrapper_files",
-      "//ash/webui/common/resources:html_wrapper_files",
-      "//ash/webui/common/resources:preprocess",
-      "//ui/webui/resources/cr_components/color_change_listener:build_ts",
-      "//ui/webui/resources/cr_elements:build_ts",
-    ]
   }
-}
 
-generate_grd("build_grd") {
-  input_files = [ "internet_config_dialog_container.html" ]
-  input_files_base_dir = rebase_path(".", "//")
-  if (optimize_webui) {
-    deps = [ ":build" ]
-    resource_path_rewrites =
-        [ "internet_config_dialog.rollup.js|internet_config_dialog.js" ]
-    manifest_files = [ "$target_gen_dir/$build_manifest" ]
-  } else {
-    deps = [ ":preprocess_generated" ]
-    manifest_files = [ "$target_gen_dir/$preprocess_gen_manifest" ]
-  }
   grd_prefix = "internet_config_dialog"
-  out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
-}
-
-preprocess_if_expr("preprocess_generated") {
-  deps = [ ":web_components" ]
-  in_folder = target_gen_dir
-  out_folder = "$target_gen_dir/$preprocess_folder"
-  out_manifest = "$target_gen_dir/$preprocess_gen_manifest"
-  in_files = [ "internet_config_dialog.js" ]
-}
-
-js_type_check("closure_compile") {
-  is_polymer3 = true
-  closure_flags = default_closure_args + mojom_js_args
-  deps = [ ":internet_config_dialog" ]
-}
-
-js_library("internet_config_dialog") {
-  deps = [
-    "//ash/webui/common/resources:assert",
-    "//ash/webui/common/resources:i18n_behavior",
-    "//ash/webui/common/resources/network:cr_policy_network_behavior_mojo",
-    "//ash/webui/common/resources/network:network_config",
-    "//ash/webui/common/resources/network:onc_mojo",
-  ]
-
-  externs_list = [
-    # TODO(crbug/1081815): Use autogenerated files instead of chrome_extensions.
-    "$externs_path/chrome_extensions.js",
-    "$externs_path/chrome_send.js",
-    "$externs_path/networking_private.js",
-    "//ui/webui/resources/cr_elements/cr_dialog/cr_dialog_externs.js",
-  ]
-  extra_sources = [ "$interfaces_path/networking_private_interface.js" ]
-}
-
-html_to_js("web_components") {
-  js_files = [ "internet_config_dialog.js" ]
-}
-
-grit("resources") {
-  defines = chrome_grit_defines
-
-  # These arguments are needed since the grd is generated at build time.
-  enable_input_discovery_for_gn_analyze = false
-  source = "$target_gen_dir/internet_config_dialog_resources.grd"
-  deps = [ ":build_grd" ]
-
-  outputs = [
-    "grit/internet_config_dialog_resources.h",
-    "grit/internet_config_dialog_resources_map.cc",
-    "grit/internet_config_dialog_resources_map.h",
-    "internet_config_dialog_resources.pak",
-  ]
-  output_dir = "$root_gen_dir/chrome"
 }
diff --git a/chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.js b/chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.ts
similarity index 75%
rename from chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.js
rename to chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.ts
index 521f7e9..43f1634b 100644
--- a/chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.js
+++ b/chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog.ts
@@ -12,11 +12,15 @@
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import './strings.m.js';
 
-import {assert} from 'chrome://resources/ash/common/assert.js';
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
 import {loadTimeData} from 'chrome://resources/ash/common/load_time_data.m.js';
+import {NetworkConfigElement} from 'chrome://resources/ash/common/network/network_config.js';
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
-import {html, mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {assert} from 'chrome://resources/js/assert.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {getTemplate} from './internet_config_dialog.html.js';
 
 /**
  * @fileoverview
@@ -25,28 +29,27 @@
  * new network from the system tray).
  */
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const InternetConfigDialogElementBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+export interface InternetConfigDialogElement {
+  $: {
+    networkConfig: NetworkConfigElement,
+    dialog: CrDialogElement,
+  };
+}
 
-/** @polymer */
+const InternetConfigDialogElementBase = I18nMixin(PolymerElement);
+
 export class InternetConfigDialogElement extends
     InternetConfigDialogElementBase {
   static get is() {
-    return 'internet-config-dialog';
+    return 'internet-config-dialog' as const;
   }
 
   static get template() {
-    return html`{__html_template__}`;
+    return getTemplate();
   }
 
   static get properties() {
     return {
-      /** @private */
       shareAllowEnable_: {
         type: Boolean,
         value() {
@@ -54,7 +57,6 @@
         },
       },
 
-      /** @private */
       shareDefault_: {
         type: Boolean,
         value() {
@@ -64,23 +66,19 @@
 
       /**
        * The network GUID to configure, or empty when configuring a new network.
-       * @private
        */
       guid_: String,
 
       /**
        * The type of network to be configured as a string. May be set initially
        * or updated by network-config.
-       * @private
        */
       type_: String,
 
-      /** @private */
       enableConnect_: Boolean,
 
       /**
        * Set by network-config when a configuration error occurs.
-       * @private
        */
       error_: {
         type: String,
@@ -89,8 +87,14 @@
     };
   }
 
-  /** @override */
-  connectedCallback() {
+  private shareAllowEnable_: boolean;
+  private shareDefault_: boolean;
+  private guid_: string;
+  private type_: string;
+  private enableConnect_: boolean;
+  private error_: string;
+
+  override connectedCallback() {
     super.connectedCallback();
 
     const isJellyEnabled = loadTimeData.valueExists('isJellyEnabled') &&
@@ -115,7 +119,6 @@
       document.head.appendChild(link);
       document.body.classList.add('jelly-enabled');
 
-      /** @suppress {checkTypes} */
       (function() {
         ColorChangeUpdater.forDocument().start();
       })();
@@ -123,41 +126,30 @@
 
     this.$.networkConfig.init();
 
-    /** @type {!CrDialogElement} */ (this.$.dialog).showModal();
+    this.$.dialog.showModal();
   }
 
-  /** @private */
-  close_() {
+  private close_(): void {
     chrome.send('dialogClose');
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getDialogTitle_() {
+  private getDialogTitle_(): string {
     const type = this.i18n('OncType' + this.type_);
     return this.i18n('internetJoinType', type);
   }
 
-  /**
-   * @return {string}
-   * @private
-   */
-  getError_() {
+  private getError_(): string {
     if (this.i18nExists(this.error_)) {
       return this.i18n(this.error_);
     }
     return this.i18n('networkErrorUnknown');
   }
 
-  /** @private */
-  onCancelClick_() {
+  private onCancelClick_(): void {
     this.close_();
   }
 
-  /** @private */
-  onConnectClick_() {
+  private onConnectClick_(): void {
     this.$.networkConfig.connect();
   }
 }
diff --git a/chrome/browser/resources/compose/app.html b/chrome/browser/resources/compose/app.html
index d346ad6..75f1fb7 100644
--- a/chrome/browser/resources/compose/app.html
+++ b/chrome/browser/resources/compose/app.html
@@ -432,7 +432,8 @@
           <select class="md-select" id="lengthMenu" value="[[selectedLength_]]"
               aria-label="$i18n{lengthMenuTitle}" on-change="onLengthChanged_">
             <template is="dom-repeat" items="[[lengthOptions_]]">
-              <option value="[[item.value]]" hidden$="[[item.hidden]]">
+              <option value="[[item.value]]" disabled$="[[item.isDefault]]"
+                  selected$="[[item.isDefault]]">
                 [[item.label]]
               </option>
             </template>
@@ -440,7 +441,8 @@
           <select class="md-select" id="toneMenu" value="[[selectedTone_]]"
               aria-label="$i18n{toneMenuTitle}" on-change="onToneChanged_">
             <template is="dom-repeat" items="[[toneOptions_]]">
-              <option value="[[item.value]]" hidden$="[[item.hidden]]">
+              <option value="[[item.value]]" disabled$="[[item.isDefault]]"
+                  selected$="[[item.isDefault]]">
                 [[item.label]]
               </option>
             </template>
diff --git a/chrome/browser/resources/compose/app.ts b/chrome/browser/resources/compose/app.ts
index f6b8aba..34ddd776 100644
--- a/chrome/browser/resources/compose/app.ts
+++ b/chrome/browser/resources/compose/app.ts
@@ -149,7 +149,7 @@
             {
               value: Length.kUnset,
               label: loadTimeData.getString('lengthMenuTitle'),
-              hidden: true,
+              isDefault: true,
             },
             {
               value: Length.kShorter,
@@ -169,7 +169,7 @@
             {
               value: Tone.kUnset,
               label: loadTimeData.getString('toneMenuTitle'),
-              hidden: true,
+              isDefault: true,
             },
             {
               value: Tone.kCasual,
diff --git a/chrome/browser/resources/password_manager/password_manager_app.html b/chrome/browser/resources/password_manager/password_manager_app.html
index d9a935f..e1928d8 100644
--- a/chrome/browser/resources/password_manager/password_manager_app.html
+++ b/chrome/browser/resources/password_manager/password_manager_app.html
@@ -48,11 +48,11 @@
     padding-bottom: 28px;
   }
 
-  /* The breakpoint of 1200px was decided on by the rounded sum of sidebar
+  /* The breakpoint of 980px was decided on by the rounded sum of sidebar
    * width, search bar width and help button width with very large font. Smaller
    * value will force overlapping of search bar and page tittle.
    */
-  @media (max-width: 1200px) {
+  @media (max-width: 980px) {
     #content {
       min-width: auto;
       /* Add some padding to make room for borders and to prevent focus
@@ -80,7 +80,7 @@
   }
 </style>
 <settings-prefs id="prefs" prefs="{{prefs_}}"></settings-prefs>
-<password-manager-toolbar id="toolbar" narrow="[[narrow_]]"
+<password-manager-toolbar id="toolbar" narrow="[[narrow_]]" page-name="[[pageTitle_]]"
     on-search-enter-click="onSearchEnterClick_">
 </password-manager-toolbar>
 <div id="container" role="group">
@@ -129,9 +129,12 @@
     </template>
   </div>
 </cr-drawer>
-<iron-media-query query="(max-width: 1200px)"
+<iron-media-query query="(max-width: 980px)"
     query-matches="{{narrow_}}">
 </iron-media-query>
+<iron-media-query query="(max-width: 1200px)"
+    query-matches="{{collapsed_}}">
+</iron-media-query>
 <cr-toast id="removalToast" duration="5000">
   <span id="removalNotification">[[toastMessage_]]</span>
   <cr-button id="undo-removal" aria-label="$i18n{undoDescription}"
diff --git a/chrome/browser/resources/password_manager/password_manager_app.ts b/chrome/browser/resources/password_manager/password_manager_app.ts
index 8d1888b..7e84b37 100644
--- a/chrome/browser/resources/password_manager/password_manager_app.ts
+++ b/chrome/browser/resources/password_manager/password_manager_app.ts
@@ -93,7 +93,16 @@
 
       narrow_: {
         type: Boolean,
-        observer: 'onNarrowChanged_',
+        observer: 'onMaxWidthChanged_',
+      },
+
+      collapsed_: {
+        type: Boolean,
+        observer: 'onMaxWidthChanged_',
+      },
+
+      pageTitle_: {
+        type: String,
       },
 
       /*
@@ -129,6 +138,8 @@
 
   private selectedPage_: Page;
   private narrow_: boolean;
+  private collapsed_: boolean;
+  private pageTitle_: string = this.i18n('passwordManagerTitle');
   private toastMessage_: string;
   private showUndo_: boolean;
   private focusConfig_: FocusConfig;
@@ -214,10 +225,16 @@
     return this.$.toolbar.searchField.isSearchFocused();
   }
 
-  private onNarrowChanged_() {
+  private onMaxWidthChanged_() {
     if (this.$.drawer.open && !this.narrow_) {
       this.$.drawer.close();
     }
+    // Window is greater than 980px but less than 1200px.
+    if (!this.narrow_ && this.collapsed_) {
+      this.pageTitle_ = this.i18n('passwordManagerString');
+    } else {
+      this.pageTitle_ = this.i18n('passwordManagerTitle');
+    }
   }
 
   private onMenuButtonClick_() {
diff --git a/chrome/browser/resources/password_manager/toolbar.html b/chrome/browser/resources/password_manager/toolbar.html
index acb64bba..66c834ac 100644
--- a/chrome/browser/resources/password_manager/toolbar.html
+++ b/chrome/browser/resources/password_manager/toolbar.html
@@ -18,11 +18,11 @@
   }
 </style>
 <cr-toolbar id="mainToolbar" on-keydown="onKeyDown_"
-    page-name="$i18n{passwordManagerString}" clear-label="$i18n{clearSearch}"
+    page-name="[[pageName]]" clear-label="$i18n{clearSearch}"
     search-prompt="$i18n{searchPrompt}" menu-label="$i18n{menuButtonLabel}"
     autofocus on-search-changed="onSearchChanged_"
     role="banner" show-menu="[[narrow]]" narrow="[[narrow]]"
-    narrow-threshold="1200">
+    narrow-threshold="980">
   <picture slot="product-logo">
     <img id="product-logo"
         srcset="chrome://password-manager/images/password_manager_logo.svg"
diff --git a/chrome/browser/resources/password_manager/toolbar.ts b/chrome/browser/resources/password_manager/toolbar.ts
index dd0364d9..35368238 100644
--- a/chrome/browser/resources/password_manager/toolbar.ts
+++ b/chrome/browser/resources/password_manager/toolbar.ts
@@ -42,10 +42,12 @@
   static get properties() {
     return {
       narrow: Boolean,
+      pageName: String,
     };
   }
 
   narrow: boolean;
+  pageName: string;
 
   override currentRouteChanged(newRoute: Route, _oldRoute: Route): void {
     this.updateSearchTerm(newRoute.queryParameters);
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html
index 7b282be..314a799 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html
+++ b/chrome/browser/resources/settings/search_engines_page/search_engine_entry.html
@@ -3,6 +3,20 @@
         font-weight: 500;
       }
 
+      .additional-info-column-group {
+        align-items: center;
+        display: flex;
+        flex: 6;
+      }
+
+      #controls-column-group {
+        flex: auto;
+        margin-left: auto;
+        display: flex;
+        justify-content: end;
+        align-items: center;
+      }
+
       cr-policy-indicator {
         display: inline-flex;
         justify-content: center;
@@ -19,12 +33,12 @@
       }
 
       #shortcut-column {
-        flex: 4;
         word-break: break-word;
       }
 
+      #shortcut-column,
       #url-column-padded {
-        flex: 3;
+        flex: auto;
         margin-inline-end: 40px;
       }
     </style>
@@ -36,44 +50,46 @@
         </site-favicon>
         <div>[[engine.displayName]]</div>
       </span>
-      <span role="cell" id="shortcut-column" hidden="[[!showShortcut]]">
-        <div>[[engine.keyword]]</div>
-      </span>
-      <span role="cell" id="url-column-padded" class="text-elide"
-          hidden="[[!showQueryUrl]]">
-        <div>[[engine.url]]</div>
-      </span>
-      <span role="cell">
-        <cr-button class="secondary-button" on-click="onActivateClick_"
-            hidden="[[!engine.canBeActivated]]" id="activate">
-          $i18n{searchEnginesActivate}
-        </cr-button>
-        <cr-icon-button class="icon-edit" on-click="onEditClick_"
-            title="$i18n{edit}" hidden="[[!showEditIcon_]]"
-            disabled$="[[!engine.canBeEdited]]" id="editIconButton">
-        </cr-icon-button>
-        <cr-icon-button class="icon-more-vert" on-click="onDotsClick_"
-            disabled$="[[engine.default]]" title="$i18n{moreActions}"
-            hidden="[[engine.isManaged]]">
-        </cr-icon-button>
-        <cr-action-menu role-description="$i18n{menu}">
-          <button class="dropdown-item" on-click="onMakeDefaultClick_"
-              disabled$="[[!engine.canBeDefault]]" id="makeDefault">
-            $i18n{searchEnginesMakeDefault}
-          </button>
-          <button class="dropdown-item" on-click="onDeactivateClick_"
-              hidden="[[!engine.canBeDeactivated]]" id="deactivate">
-            $i18n{searchEnginesDeactivate}
-          </button>
-          <button class="dropdown-item" on-click="onDeleteClick_"
-              hidden="[[!engine.canBeRemoved]]" id="delete">
-            $i18n{delete}
-          </button>
-        </cr-action-menu>
-        <template is="dom-if" if="[[engine.isManaged]]">
-          <cr-policy-indicator indicator-type="userPolicy">
-          </cr-policy-indicator>
-        </dom-if>
+      <span class="additional-info-column-group">
+        <span role="cell" id="shortcut-column" hidden="[[!showShortcut]]">
+          <div>[[engine.keyword]]</div>
+        </span>
+        <span role="cell" id="url-column-padded" class="text-elide"
+            hidden="[[!showQueryUrl]]">
+          <div>[[engine.url]]</div>
+        </span>
+        <span role="cell" id="controls-column-group">
+          <cr-button class="secondary-button" on-click="onActivateClick_"
+              hidden="[[!engine.canBeActivated]]" id="activate">
+            $i18n{searchEnginesActivate}
+          </cr-button>
+          <cr-icon-button class="icon-edit" on-click="onEditClick_"
+              title="$i18n{edit}" hidden="[[!showEditIcon_]]"
+              disabled$="[[!engine.canBeEdited]]" id="editIconButton">
+          </cr-icon-button>
+          <cr-icon-button class="icon-more-vert" on-click="onDotsClick_"
+              disabled$="[[engine.default]]" title="$i18n{moreActions}"
+              hidden="[[engine.isManaged]]">
+          </cr-icon-button>
+          <cr-action-menu role-description="$i18n{menu}">
+            <button class="dropdown-item" on-click="onMakeDefaultClick_"
+                disabled$="[[!engine.canBeDefault]]" id="makeDefault">
+              $i18n{searchEnginesMakeDefault}
+            </button>
+            <button class="dropdown-item" on-click="onDeactivateClick_"
+                hidden="[[!engine.canBeDeactivated]]" id="deactivate">
+              $i18n{searchEnginesDeactivate}
+            </button>
+            <button class="dropdown-item" on-click="onDeleteClick_"
+                hidden="[[!engine.canBeRemoved]]" id="delete">
+              $i18n{delete}
+            </button>
+          </cr-action-menu>
+          <template is="dom-if" if="[[engine.isManaged]]">
+            <cr-policy-indicator indicator-type="userPolicy">
+            </cr-policy-indicator>
+          </dom-if>
+        </span>
       </span>
     </div>
     <template is="dom-if" if="[[engine.extension]]">
diff --git a/chrome/browser/resources/settings/search_engines_page/search_engines_list.html b/chrome/browser/resources/settings/search_engines_page/search_engines_list.html
index 32e74ad4e..5d53d86 100644
--- a/chrome/browser/resources/settings/search_engines_page/search_engines_list.html
+++ b/chrome/browser/resources/settings/search_engines_page/search_engines_list.html
@@ -4,6 +4,20 @@
         padding: 10px 0;
       }
 
+      #headers .additional-info-column-group {
+        align-items: center;
+        display: flex;
+        flex: 6;
+      }
+
+      #headers .controls-group {
+        flex: auto;
+        margin-left: auto;
+        display: flex;
+        justify-content: end;
+        align-items: center;
+      }
+
       #headers .name {
         flex: 3;
       }
@@ -11,7 +25,8 @@
       #headers .shortcut,
       #headers .url,
       #headers .url-padded {
-        flex: 4;
+        flex: auto;
+        margin-inline-end: 40px;
       }
 
       settings-search-engine-entry {
@@ -40,15 +55,20 @@
       <div role="rowgroup">
         <div role="row" id="headers" class="column-header">
           <span class="name" role="columnheader">[[nameColumnHeader]]</span>
-          <span class="shortcut" role="columnheader" hidden="[[!showShortcut]]">
-            $i18n{searchEnginesShortcut}
+          <span class="additional-info-column-group">
+            <span class="shortcut" role="columnheader"
+                  hidden="[[!showShortcut]]">
+              $i18n{searchEnginesShortcut}
+            </span>
+            <span class="url-padded" role="columnheader"
+                  hidden="[[!showQueryUrl]]">
+              $i18n{searchEnginesQueryURL}
+            </span>
+            <span class="controls-group">
+              <span class="icon-placeholder"></span>
+              <span class="icon-placeholder"></span>
+            </span>
           </span>
-          <span class="url-padded" role="columnheader"
-                hidden="[[!showQueryUrl]]">
-            $i18n{searchEnginesQueryURL}
-          </span>
-          <span class="icon-placeholder"></span>
-          <span class="icon-placeholder"></span>
         </div>
       </div>
       <template is="dom-if" if="[[!collapseList]]">
diff --git a/chrome/browser/signin/dice_tab_helper.cc b/chrome/browser/signin/dice_tab_helper.cc
index fb750e0..171b7c2 100644
--- a/chrome/browser/signin/dice_tab_helper.cc
+++ b/chrome/browser/signin/dice_tab_helper.cc
@@ -23,25 +23,23 @@
 // static
 DiceTabHelper::EnableSyncCallback
 DiceTabHelper::GetEnableSyncCallbackForBrowser() {
-  return base::BindRepeating(
-      [](Profile* profile, signin_metrics::AccessPoint access_point,
-         signin_metrics::PromoAction promo_action,
-         signin_metrics::Reason reason, content::WebContents* web_contents,
-         const CoreAccountInfo& account_info) {
-        DCHECK(profile);
-        Browser* browser = web_contents
-                               ? chrome::FindBrowserWithTab(web_contents)
-                               : chrome::FindBrowserWithProfile(profile);
-        if (!browser) {
-          return;
-        }
-        // TurnSyncOnHelper is suicidal (it will kill itself once it
-        // finishes enabling sync).
-        new TurnSyncOnHelper(
-            profile, browser, access_point, promo_action, reason,
-            account_info.account_id,
-            TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT);
-      });
+  return base::BindRepeating([](Profile* profile,
+                                signin_metrics::AccessPoint access_point,
+                                signin_metrics::PromoAction promo_action,
+                                content::WebContents* web_contents,
+                                const CoreAccountInfo& account_info) {
+    DCHECK(profile);
+    Browser* browser = web_contents ? chrome::FindBrowserWithTab(web_contents)
+                                    : chrome::FindBrowserWithProfile(profile);
+    if (!browser) {
+      return;
+    }
+    // TurnSyncOnHelper is suicidal (it will kill itself once it
+    // finishes enabling sync).
+    new TurnSyncOnHelper(profile, browser, access_point, promo_action,
+                         account_info.account_id,
+                         TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT);
+  });
 }
 
 // static
diff --git a/chrome/browser/signin/dice_tab_helper.h b/chrome/browser/signin/dice_tab_helper.h
index 4d074394..5a2a337 100644
--- a/chrome/browser/signin/dice_tab_helper.h
+++ b/chrome/browser/signin/dice_tab_helper.h
@@ -27,7 +27,6 @@
       base::RepeatingCallback<void(Profile*,
                                    signin_metrics::AccessPoint,
                                    signin_metrics::PromoAction,
-                                   signin_metrics::Reason,
                                    content::WebContents*,
                                    const CoreAccountInfo&)>;
 
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl.cc b/chrome/browser/signin/process_dice_header_delegate_impl.cc
index 6f45700f..ea69505 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl.cc
+++ b/chrome/browser/signin/process_dice_header_delegate_impl.cc
@@ -83,7 +83,6 @@
       signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN;
   signin_metrics::PromoAction promo_action =
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO;
-  signin_metrics::Reason reason = signin_metrics::Reason::kUnknownReason;
   GURL redirect_url;
   EnableSyncCallback enable_sync_callback;
   OnSigninHeaderReceived on_signin_header_received;
@@ -95,7 +94,6 @@
     redirect_url = tab_helper->redirect_url();
     access_point = tab_helper->signin_access_point();
     promo_action = tab_helper->signin_promo_action();
-    reason = tab_helper->signin_reason();
     // `show_signin_error_callback` may be null if the `DiceTabHelper` was reset
     // after completion of a signin flow.
     show_signin_error_callback =
@@ -118,7 +116,7 @@
   }
 
   return std::make_unique<ProcessDiceHeaderDelegateImpl>(
-      web_contents, is_sync_signin_tab, access_point, promo_action, reason,
+      web_contents, is_sync_signin_tab, access_point, promo_action,
       std::move(redirect_url), std::move(enable_sync_callback),
       std::move(on_signin_header_received),
       std::move(show_signin_error_callback));
@@ -129,7 +127,6 @@
     bool is_sync_signin_tab,
     signin_metrics::AccessPoint access_point,
     signin_metrics::PromoAction promo_action,
-    signin_metrics::Reason reason,
     GURL redirect_url,
     EnableSyncCallback enable_sync_callback,
     OnSigninHeaderReceived on_signin_header_received,
@@ -140,7 +137,6 @@
       is_sync_signin_tab_(is_sync_signin_tab),
       access_point_(access_point),
       promo_action_(promo_action),
-      reason_(reason),
       redirect_url_(std::move(redirect_url)),
       enable_sync_callback_(std::move(enable_sync_callback)),
       on_signin_header_received_(std::move(on_signin_header_received)),
@@ -202,7 +198,7 @@
 
   VLOG(1) << "Start sync after web sign-in.";
   std::move(enable_sync_callback_)
-      .Run(&profile_.get(), access_point_, promo_action_, reason_, web_contents,
+      .Run(&profile_.get(), access_point_, promo_action_, web_contents,
            account_info);
 
   Redirect();
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl.h b/chrome/browser/signin/process_dice_header_delegate_impl.h
index 18672f14..be70368 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl.h
+++ b/chrome/browser/signin/process_dice_header_delegate_impl.h
@@ -31,7 +31,6 @@
       base::OnceCallback<void(Profile*,
                               signin_metrics::AccessPoint,
                               signin_metrics::PromoAction,
-                              signin_metrics::Reason,
                               content::WebContents*,
                               const CoreAccountInfo&)>;
 
@@ -58,7 +57,6 @@
       bool is_sync_signin_tab,
       signin_metrics::AccessPoint access_point,
       signin_metrics::PromoAction promo_action,
-      signin_metrics::Reason reason,
       GURL redirect_url,
       EnableSyncCallback enable_sync_callback,
       OnSigninHeaderReceived on_signin_header_received,
@@ -91,7 +89,6 @@
   const bool is_sync_signin_tab_;
   const signin_metrics::AccessPoint access_point_;
   const signin_metrics::PromoAction promo_action_;
-  const signin_metrics::Reason reason_;
   const GURL redirect_url_;
   EnableSyncCallback enable_sync_callback_;
   OnSigninHeaderReceived on_signin_header_received_;
diff --git a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
index 00f7789..abb6573 100644
--- a/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
+++ b/chrome/browser/signin/process_dice_header_delegate_impl_unittest.cc
@@ -170,7 +170,7 @@
       return std::make_unique<ProcessDiceHeaderDelegateImpl>(
           web_contents(), /*is_sync_signin_tab=*/false,
           signin_metrics::AccessPoint::ACCESS_POINT_WEB_SIGNIN,
-          kTestPromoAction, signin_metrics::Reason::kUnknownReason, GURL(),
+          kTestPromoAction, GURL(),
           ProcessDiceHeaderDelegateImpl::EnableSyncCallback(),
           base::BindRepeating(
               &ProcessDiceHeaderDelegateImplTest::OnSigninHeaderReceived,
@@ -200,13 +200,11 @@
   void StartSyncCallback(Profile* profile,
                          signin_metrics::AccessPoint access_point,
                          signin_metrics::PromoAction promo_action,
-                         signin_metrics::Reason reason,
                          content::WebContents* contents,
                          const CoreAccountInfo& account_info) {
     EXPECT_EQ(profile, this->profile());
     EXPECT_EQ(access_point, kTestAccessPoint);
     EXPECT_EQ(promo_action, kTestPromoAction);
-    EXPECT_EQ(reason, signin_reason_);
     EXPECT_EQ(web_contents(), contents);
     EXPECT_EQ(account_info_, account_info);
     enable_sync_called_ = true;
diff --git a/chrome/browser/signin/signin_ui_delegate.cc b/chrome/browser/signin/signin_ui_delegate.cc
index eed80c0..b374286 100644
--- a/chrome/browser/signin/signin_ui_delegate.cc
+++ b/chrome/browser/signin/signin_ui_delegate.cc
@@ -13,14 +13,12 @@
     Profile* profile,
     signin_metrics::AccessPoint access_point,
     signin_metrics::PromoAction promo_action,
-    signin_metrics::Reason signin_reason,
     const CoreAccountId& account_id,
     TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode) {
   // TurnSyncOnHelper is suicidal (it will delete itself once it finishes
   // enabling sync).
   new TurnSyncOnHelper(profile, EnsureBrowser(profile), access_point,
-                       promo_action, signin_reason, account_id,
-                       signin_aborted_mode);
+                       promo_action, account_id, signin_aborted_mode);
 }
 
 // static
diff --git a/chrome/browser/signin/signin_ui_delegate.h b/chrome/browser/signin/signin_ui_delegate.h
index a4b8239..ec6fb92d 100644
--- a/chrome/browser/signin/signin_ui_delegate.h
+++ b/chrome/browser/signin/signin_ui_delegate.h
@@ -49,7 +49,6 @@
       Profile* profile,
       signin_metrics::AccessPoint access_point,
       signin_metrics::PromoAction promo_action,
-      signin_metrics::Reason signin_reason,
       const CoreAccountId& account_id,
       TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode);
 
diff --git a/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc b/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
index 4842e32..22bb4ed 100644
--- a/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
+++ b/chrome/browser/signin/signin_ui_delegate_impl_lacros.cc
@@ -164,10 +164,7 @@
   if (!browser)
     return;
 
-  ShowTurnSyncOnUI(profile, access_point, promo_action,
-                   is_reauth ? signin_metrics::Reason::kReauthentication
-                             : signin_metrics::Reason::kSigninPrimaryAccount,
-                   account_id,
+  ShowTurnSyncOnUI(profile, access_point, promo_action, account_id,
                    is_reauth
                        ? TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT
                        : TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT);
diff --git a/chrome/browser/signin/signin_ui_util.cc b/chrome/browser/signin/signin_ui_util.cc
index cad650ec..2b4f936 100644
--- a/chrome/browser/signin/signin_ui_util.cc
+++ b/chrome/browser/signin/signin_ui_util.cc
@@ -366,8 +366,7 @@
                                               existing_account_promo_action);
   signin_metrics::RecordSigninUserActionForAccessPoint(access_point);
   GetSigninUiDelegate()->ShowTurnSyncOnUI(
-      profile, access_point, existing_account_promo_action,
-      signin_metrics::Reason::kSigninPrimaryAccount, account.account_id,
+      profile, access_point, existing_account_promo_action, account.account_id,
       signin_aborted_mode);
 #else
   NOTREACHED();
diff --git a/chrome/browser/signin/signin_ui_util_unittest.cc b/chrome/browser/signin/signin_ui_util_unittest.cc
index 8146e44..504cbaa 100644
--- a/chrome/browser/signin/signin_ui_util_unittest.cc
+++ b/chrome/browser/signin/signin_ui_util_unittest.cc
@@ -106,7 +106,6 @@
               (Profile * profile,
                signin_metrics::AccessPoint access_point,
                signin_metrics::PromoAction promo_action,
-               signin_metrics::Reason signin_reason,
                const CoreAccountId& account_id,
                TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode),
               ());
@@ -121,7 +120,6 @@
               (Profile * profile,
                signin_metrics::AccessPoint access_point,
                signin_metrics::PromoAction promo_action,
-               signin_metrics::Reason signin_reason,
                const CoreAccountId& account_id,
                TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode),
               ());
@@ -160,13 +158,11 @@
   void ExpectTurnSyncOn(
       signin_metrics::AccessPoint access_point,
       signin_metrics::PromoAction promo_action,
-      signin_metrics::Reason signin_reason,
       const CoreAccountId& account_id,
       TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode) {
-    EXPECT_CALL(
-        mock_delegate_,
-        ShowTurnSyncOnUI(profile(), access_point, promo_action, signin_reason,
-                         account_id, signin_aborted_mode));
+    EXPECT_CALL(mock_delegate_,
+                ShowTurnSyncOnUI(profile(), access_point, promo_action,
+                                 account_id, signin_aborted_mode));
   }
 
   void ExpectNoSigninStartedHistograms(
@@ -275,8 +271,7 @@
             ? signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT
             : signin_metrics::PromoAction::PROMO_ACTION_NOT_DEFAULT;
     ExpectTurnSyncOn(signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
-                     expected_promo_action,
-                     signin_metrics::Reason::kSigninPrimaryAccount, account_id,
+                     expected_promo_action, account_id,
                      TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT);
     EnableSync(
         GetIdentityManager()->FindExtendedAccountInfoByAccountId(account_id),
@@ -712,8 +707,7 @@
             : signin_metrics::PromoAction::PROMO_ACTION_NOT_DEFAULT;
     ExpectTurnSyncOn(
         signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_BUBBLE,
-        expected_promo_action, signin_metrics::Reason::kSigninPrimaryAccount,
-        account_id,
+        expected_promo_action, account_id,
         TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT_ON_WEB_ONLY);
     EnableSync(
         GetIdentityManager()->FindExtendedAccountInfoByAccountId(account_id),
@@ -759,13 +753,11 @@
   void ExpectTurnSyncOn(
       signin_metrics::AccessPoint access_point,
       signin_metrics::PromoAction promo_action,
-      signin_metrics::Reason signin_reason,
       const CoreAccountId& account_id,
       TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode) {
-    EXPECT_CALL(
-        mock_delegate_,
-        ShowTurnSyncOnUI(profile(), access_point, promo_action, signin_reason,
-                         account_id, signin_aborted_mode));
+    EXPECT_CALL(mock_delegate_,
+                ShowTurnSyncOnUI(profile(), access_point, promo_action,
+                                 account_id, signin_aborted_mode));
   }
 
  protected:
@@ -790,8 +782,7 @@
 
     ExpectTurnSyncOn(
         signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN,
-        expected_promo_action, signin_metrics::Reason::kSigninPrimaryAccount,
-        account_info.account_id,
+        expected_promo_action, account_info.account_id,
         TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT);
     EnableSyncFromMultiAccountPromo(
         profile(), account_info,
diff --git a/chrome/browser/signin/signin_util_win.cc b/chrome/browser/signin/signin_util_win.cc
index ccb06b2..8cf7cbc 100644
--- a/chrome/browser/signin/signin_util_win.cc
+++ b/chrome/browser/signin/signin_util_win.cc
@@ -88,15 +88,13 @@
   if (GetTurnSyncOnHelperDelegateForTestingStorage()->get()) {
     new TurnSyncOnHelper(
         profile, kCredentialsProviderAccessPointWin,
-        signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT,
-        signin_metrics::Reason::kSigninPrimaryAccount, account_id,
+        signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT, account_id,
         TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT,
         std::move(*GetTurnSyncOnHelperDelegateForTestingStorage()),
         base::DoNothing());
   } else {
     new TurnSyncOnHelper(profile, browser, kCredentialsProviderAccessPointWin,
                          signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT,
-                         signin_metrics::Reason::kSigninPrimaryAccount,
                          account_id,
                          TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT);
   }
diff --git a/chrome/browser/sync/test/integration/password_sharing_invitation_helper.cc b/chrome/browser/sync/test/integration/password_sharing_invitation_helper.cc
new file mode 100644
index 0000000..a983cd4
--- /dev/null
+++ b/chrome/browser/sync/test/integration/password_sharing_invitation_helper.cc
@@ -0,0 +1,133 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/test/integration/password_sharing_invitation_helper.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/uuid.h"
+#include "components/sync/engine/nigori/cross_user_sharing_public_key.h"
+#include "components/sync/nigori/cryptographer_impl.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace password_sharing_helper {
+
+namespace {
+constexpr char kSignonRealm[] = "signon_realm";
+constexpr char kOrigin[] = "http://abc.com/";
+constexpr char kUsernameElement[] = "username_element";
+constexpr char kPasswordElement[] = "password_element";
+constexpr char kPasswordDisplayName[] = "password_display_name";
+constexpr char kPasswordAvatarUrl[] = "http://avatar.url/";
+
+constexpr char kSenderEmail[] = "sender@gmail.com";
+constexpr char kSenderDisplayName[] = "Sender Name";
+constexpr char kSenderProfileImageUrl[] = "http://sender.url/image";
+
+constexpr int kSenderKeyVersion = 0;
+
+sync_pb::CrossUserSharingPublicKey PublicKeyToProto(
+    const syncer::CrossUserSharingPublicKey& public_key) {
+  sync_pb::CrossUserSharingPublicKey proto;
+  auto raw_public_key = public_key.GetRawPublicKey();
+  proto.set_x25519_public_key(
+      std::string(raw_public_key.begin(), raw_public_key.end()));
+  proto.set_version(kSenderKeyVersion);
+  return proto;
+}
+
+// Encrypts the invitation data to simulate the sending client.
+std::vector<uint8_t> EncryptInvitationData(
+    const sync_pb::PasswordSharingInvitationData& unencrypted_password_data,
+    const sync_pb::CrossUserSharingPublicKey& recipient_public_key,
+    const syncer::CrossUserSharingPublicPrivateKeyPair& sender_key_pair) {
+  std::unique_ptr<syncer::CryptographerImpl> sender_cryptographer =
+      syncer::CryptographerImpl::CreateEmpty();
+
+  // Clone `sender_key_pair` since the cryptographer requires it to be moved.
+  absl::optional<syncer::CrossUserSharingPublicPrivateKeyPair>
+      sender_key_pair_copy =
+          syncer::CrossUserSharingPublicPrivateKeyPair::CreateByImport(
+              sender_key_pair.GetRawPrivateKey());
+  CHECK(sender_key_pair_copy);
+  sender_cryptographer->EmplaceKeyPair(std::move(sender_key_pair_copy.value()),
+                                       kSenderKeyVersion);
+  sender_cryptographer->SelectDefaultCrossUserSharingKey(kSenderKeyVersion);
+
+  std::string serialized_data;
+  bool success = unencrypted_password_data.SerializeToString(&serialized_data);
+  CHECK(success);
+
+  absl::optional<std::vector<uint8_t>> result =
+      sender_cryptographer->AuthEncryptForCrossUserSharing(
+          base::as_bytes(base::make_span(serialized_data)),
+          base::as_bytes(
+              base::make_span(recipient_public_key.x25519_public_key())));
+  CHECK(result);
+
+  return result.value();
+}
+}  // namespace
+
+sync_pb::IncomingPasswordSharingInvitationSpecifics
+CreateEncryptedIncomingInvitationSpecifics(
+    const sync_pb::PasswordSharingInvitationData& invitation_data,
+    const sync_pb::UserDisplayInfo& sender_display_info,
+    const sync_pb::CrossUserSharingPublicKey& recipient_public_key,
+    const syncer::CrossUserSharingPublicPrivateKeyPair& sender_key_pair) {
+  sync_pb::IncomingPasswordSharingInvitationSpecifics specifics;
+  std::vector<uint8_t> encrypted_password = EncryptInvitationData(
+      invitation_data, recipient_public_key, sender_key_pair);
+  specifics.set_encrypted_password_sharing_invitation_data(
+      std::string(encrypted_password.begin(), encrypted_password.end()));
+  specifics.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
+  specifics.set_recipient_key_version(recipient_public_key.version());
+
+  absl::optional<syncer::CrossUserSharingPublicKey> sender_public_key =
+      syncer::CrossUserSharingPublicKey::CreateByImport(
+          sender_key_pair.GetRawPublicKey());
+  CHECK(sender_public_key);
+
+  sync_pb::UserInfo* sender_info = specifics.mutable_sender_info();
+  sender_info->mutable_cross_user_sharing_public_key()->CopyFrom(
+      PublicKeyToProto(sender_public_key.value()));
+  sender_info->mutable_user_display_info()->CopyFrom(sender_display_info);
+
+  return specifics;
+}
+
+sync_pb::PasswordSharingInvitationData CreateDefaultIncomingInvitation(
+    const std::string& username_value,
+    const std::string& password_value) {
+  sync_pb::PasswordSharingInvitationData password_invitation_data;
+  sync_pb::PasswordSharingInvitationData::PasswordGroupData*
+      password_group_data =
+          password_invitation_data.mutable_password_group_data();
+
+  password_group_data->set_username_value(username_value);
+  password_group_data->set_password_value(password_value);
+
+  sync_pb::PasswordSharingInvitationData::PasswordGroupElementData*
+      element_data = password_group_data->add_element_data();
+
+  element_data->set_signon_realm(kSignonRealm);
+  element_data->set_origin(kOrigin);
+  element_data->set_username_element(kUsernameElement);
+  element_data->set_password_element(kPasswordElement);
+  element_data->set_display_name(kPasswordDisplayName);
+  element_data->set_avatar_url(kPasswordAvatarUrl);
+
+  return password_invitation_data;
+}
+
+sync_pb::UserDisplayInfo CreateDefaultSenderDisplayInfo() {
+  sync_pb::UserDisplayInfo sender_display_info;
+  sender_display_info.set_email(kSenderEmail);
+  sender_display_info.set_display_name(kSenderDisplayName);
+  sender_display_info.set_profile_image_url(kSenderProfileImageUrl);
+  return sender_display_info;
+}
+
+}  // namespace password_sharing_helper
diff --git a/chrome/browser/sync/test/integration/password_sharing_invitation_helper.h b/chrome/browser/sync/test/integration/password_sharing_invitation_helper.h
new file mode 100644
index 0000000..0bb65166
--- /dev/null
+++ b/chrome/browser/sync/test/integration/password_sharing_invitation_helper.h
@@ -0,0 +1,36 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_PASSWORD_SHARING_INVITATION_HELPER_H_
+#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_PASSWORD_SHARING_INVITATION_HELPER_H_
+
+#include <string>
+
+#include "components/sync/engine/nigori/cross_user_sharing_public_private_key_pair.h"
+#include "components/sync/protocol/nigori_specifics.pb.h"
+#include "components/sync/protocol/password_sharing_invitation_specifics.pb.h"
+
+namespace password_sharing_helper {
+
+// Creates an incoming password sharing invitation specifics including
+// encrypting the `invitation_data`.
+sync_pb::IncomingPasswordSharingInvitationSpecifics
+CreateEncryptedIncomingInvitationSpecifics(
+    const sync_pb::PasswordSharingInvitationData& invitation_data,
+    const sync_pb::UserDisplayInfo& sender_display_info,
+    const sync_pb::CrossUserSharingPublicKey& recipient_public_key,
+    const syncer::CrossUserSharingPublicPrivateKeyPair& sender_key_pair);
+
+// Creates incoming password sharing invitation data with the given password
+// value.
+sync_pb::PasswordSharingInvitationData CreateDefaultIncomingInvitation(
+    const std::string& username_value,
+    const std::string& password_value);
+
+// Creates default UserDisplayInfo for sender.
+sync_pb::UserDisplayInfo CreateDefaultSenderDisplayInfo();
+
+}  // namespace password_sharing_helper
+
+#endif  // CHROME_BROWSER_SYNC_TEST_INTEGRATION_PASSWORD_SHARING_INVITATION_HELPER_H_
diff --git a/chrome/browser/sync/test/integration/passwords_helper.cc b/chrome/browser/sync/test/integration/passwords_helper.cc
index 7904514..7e7008b 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.cc
+++ b/chrome/browser/sync/test/integration/passwords_helper.cc
@@ -29,6 +29,7 @@
 #include "components/sync/nigori/cryptographer_impl.h"
 #include "components/sync/protocol/entity_specifics.pb.h"
 #include "components/sync/protocol/password_specifics.pb.h"
+#include "components/sync/service/sync_service_impl.h"
 #include "content/public/test/test_utils.h"
 #include "url/gurl.h"
 
@@ -69,6 +70,8 @@
  private:
   // This RunLoop uses kNestableTasksAllowed because it runs nested within
   // another RunLoop.
+  // TODO(crbug.com/1514434): consider changing this to PasswordStoreInterface
+  // observer to avoid nested run loops.
   base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
   std::vector<std::unique_ptr<PasswordForm>> result_;
   base::WeakPtrFactory<PasswordStoreConsumerHelper> weak_ptr_factory_{this};
@@ -486,3 +489,42 @@
   }
   return is_matching;
 }
+
+PasswordFormsAddedChecker::PasswordFormsAddedChecker(
+    password_manager::PasswordStoreInterface* password_store,
+    size_t expected_new_password_forms)
+    : password_store_(password_store),
+      expected_new_password_forms_(expected_new_password_forms) {
+  password_store_->AddObserver(this);
+}
+
+PasswordFormsAddedChecker::~PasswordFormsAddedChecker() {
+  password_store_->RemoveObserver(this);
+}
+
+bool PasswordFormsAddedChecker::IsExitConditionSatisfied(std::ostream* os) {
+  *os << "Waiting for " << expected_new_password_forms_
+      << " passwords added to the store. ";
+
+  *os << "Current number of added password forms to the store: "
+      << num_added_passwords_;
+  return num_added_passwords_ == expected_new_password_forms_;
+}
+
+void PasswordFormsAddedChecker::OnLoginsChanged(
+    password_manager::PasswordStoreInterface* store,
+    const password_manager::PasswordStoreChangeList& changes) {
+  for (const password_manager::PasswordStoreChange& change : changes) {
+    if (change.type() == password_manager::PasswordStoreChange::ADD) {
+      num_added_passwords_++;
+    }
+  }
+
+  CheckExitCondition();
+}
+
+void PasswordFormsAddedChecker::OnLoginsRetained(
+    password_manager::PasswordStoreInterface* store,
+    const std::vector<password_manager::PasswordForm>& retained_passwords) {
+  // Not used.
+}
diff --git a/chrome/browser/sync/test/integration/passwords_helper.h b/chrome/browser/sync/test/integration/passwords_helper.h
index d390584..ffd89ab 100644
--- a/chrome/browser/sync/test/integration/passwords_helper.h
+++ b/chrome/browser/sync/test/integration/passwords_helper.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_SYNC_TEST_INTEGRATION_PASSWORDS_HELPER_H_
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -14,10 +15,12 @@
 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "components/password_manager/core/browser/password_form.h"
+#include "components/password_manager/core/browser/password_store/password_store_interface.h"
 
 namespace syncer {
 class Cryptographer;
 class KeyDerivationParams;
+class SyncServiceImpl;
 }
 
 namespace password_manager {
@@ -27,7 +30,8 @@
 namespace passwords_helper {
 
 // Returns all logins from |store| matching a fake signon realm (see
-// CreateTestPasswordForm()).
+// CreateTestPasswordForm()). Note that it uses RunLoop to wait for async
+// results and should be avoided from using in StatusChangeChecker.
 // TODO(treib): Rename this to make clear how specific it is.
 std::vector<std::unique_ptr<password_manager::PasswordForm>> GetLogins(
     password_manager::PasswordStoreInterface* store);
@@ -215,4 +219,34 @@
   std::vector<std::unique_ptr<password_manager::PasswordForm>> expected_forms_;
 };
 
+// Waits for the `expected_new_password_forms` of newly added passwords to the
+// `password_store`. Note that this object should be created before any changes
+// to the store to prevent test flakiness.
+class PasswordFormsAddedChecker
+    : public StatusChangeChecker,
+      public password_manager::PasswordStoreInterface::Observer {
+ public:
+  PasswordFormsAddedChecker(
+      password_manager::PasswordStoreInterface* password_store,
+      size_t expected_new_password_forms);
+  ~PasswordFormsAddedChecker() override;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied(std::ostream* os) override;
+
+  // PasswordStoreInterface::Observer implementation.
+  void OnLoginsChanged(
+      password_manager::PasswordStoreInterface* store,
+      const password_manager::PasswordStoreChangeList& changes) override;
+  void OnLoginsRetained(password_manager::PasswordStoreInterface* store,
+                        const std::vector<password_manager::PasswordForm>&
+                            retained_passwords) override;
+
+ private:
+  const raw_ptr<password_manager::PasswordStoreInterface> password_store_;
+  const size_t expected_new_password_forms_;
+
+  size_t num_added_passwords_ = 0;
+};
+
 #endif  // CHROME_BROWSER_SYNC_TEST_INTEGRATION_PASSWORDS_HELPER_H_
diff --git a/chrome/browser/sync/test/integration/single_client_incoming_password_sharing_invitation_test.cc b/chrome/browser/sync/test/integration/single_client_incoming_password_sharing_invitation_test.cc
index 2891de7..355aaf0 100644
--- a/chrome/browser/sync/test/integration/single_client_incoming_password_sharing_invitation_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_incoming_password_sharing_invitation_test.cc
@@ -14,6 +14,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/sync/test/integration/encryption_helper.h"
 #include "chrome/browser/sync/test/integration/fake_server_match_status_checker.h"
+#include "chrome/browser/sync/test/integration/password_sharing_invitation_helper.h"
 #include "chrome/browser/sync/test/integration/passwords_helper.h"
 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
 #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
@@ -39,6 +40,9 @@
 using password_manager::PasswordStoreInterface;
 using password_manager::metrics_util::
     ProcessIncomingPasswordSharingInvitationResult;
+using password_sharing_helper::CreateDefaultIncomingInvitation;
+using password_sharing_helper::CreateDefaultSenderDisplayInfo;
+using password_sharing_helper::CreateEncryptedIncomingInvitationSpecifics;
 using passwords_helper::GetAccountPasswordStoreInterface;
 using passwords_helper::GetAllLogins;
 using passwords_helper::GetProfilePasswordStoreInterface;
@@ -56,134 +60,39 @@
 namespace {
 
 constexpr char kPasswordValue[] = "password";
-constexpr char kSignonRealm[] = "signon_realm";
-constexpr char kOrigin[] = "http://abc.com/";
-constexpr char kUsernameElement[] = "username_element";
 constexpr char kUsernameValue[] = "username";
-constexpr char kPasswordElement[] = "password_element";
-constexpr char kPasswordDisplayName[] = "password_display_name";
-constexpr char kPasswordAvatarUrl[] = "http://avatar.url/";
-
-constexpr char kSenderEmail[] = "sender@gmail.com";
-constexpr char kSenderDisplayName[] = "Sender Name";
-constexpr char kSenderProfileImageUrl[] = "http://sender.url/image";
-
-constexpr char kLocalPasswordValue[] = "local_password_value";
-
-constexpr uint32_t kSenderKeyVersion = 1;
-
-sync_pb::CrossUserSharingPublicKey PublicKeyToProto(
-    const syncer::CrossUserSharingPublicKey& public_key) {
-  sync_pb::CrossUserSharingPublicKey proto;
-  auto raw_public_key = public_key.GetRawPublicKey();
-  proto.set_x25519_public_key(
-      std::string(raw_public_key.begin(), raw_public_key.end()));
-  proto.set_version(kSenderKeyVersion);
-  return proto;
-}
-
-PasswordSharingInvitationData CreateUnencryptedInvitationData() {
-  PasswordSharingInvitationData password_invitation_data;
-  PasswordSharingInvitationData::PasswordGroupData* password_group_data =
-      password_invitation_data.mutable_password_group_data();
-
-  password_group_data->set_username_value(kUsernameValue);
-  password_group_data->set_password_value(kPasswordValue);
-
-  sync_pb::PasswordSharingInvitationData::PasswordGroupElementData*
-      element_data = password_group_data->add_element_data();
-
-  element_data->set_signon_realm(kSignonRealm);
-  element_data->set_origin(kOrigin);
-  element_data->set_username_element(kUsernameElement);
-  element_data->set_password_element(kPasswordElement);
-  element_data->set_display_name(kPasswordDisplayName);
-  element_data->set_avatar_url(kPasswordAvatarUrl);
-
-  return password_invitation_data;
-}
-
-std::vector<uint8_t> EncryptInvitationData(
-    const PasswordSharingInvitationData& unencrypted_password_data,
-    const sync_pb::CrossUserSharingPublicKey& recipient_public_key,
-    const syncer::CrossUserSharingPublicPrivateKeyPair& sender_key_pair) {
-  std::unique_ptr<syncer::CryptographerImpl> sender_cryptographer =
-      syncer::CryptographerImpl::CreateEmpty();
-
-  // Clone `sender_key_pair` since the cryptographer requires it to be moved.
-  absl::optional<syncer::CrossUserSharingPublicPrivateKeyPair>
-      sender_key_pair_copy =
-          syncer::CrossUserSharingPublicPrivateKeyPair::CreateByImport(
-              sender_key_pair.GetRawPrivateKey());
-  DCHECK(sender_key_pair_copy);
-  sender_cryptographer->EmplaceKeyPair(std::move(sender_key_pair_copy.value()),
-                                       kSenderKeyVersion);
-  sender_cryptographer->SelectDefaultCrossUserSharingKey(kSenderKeyVersion);
-
-  std::string serialized_data;
-  bool success = unencrypted_password_data.SerializeToString(&serialized_data);
-  DCHECK(success);
-
-  absl::optional<std::vector<uint8_t>> result =
-      sender_cryptographer->AuthEncryptForCrossUserSharing(
-          base::as_bytes(base::make_span(serialized_data)),
-          base::as_bytes(
-              base::make_span(recipient_public_key.x25519_public_key())));
-  DCHECK(result);
-
-  return result.value();
-}
 
 IncomingPasswordSharingInvitationSpecifics CreateInvitationSpecifics(
     const sync_pb::CrossUserSharingPublicKey& recipient_public_key) {
-  syncer::CrossUserSharingPublicPrivateKeyPair sender_key_pair =
-      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair();
-  std::vector<uint8_t> encrypted_password = EncryptInvitationData(
-      CreateUnencryptedInvitationData(), recipient_public_key, sender_key_pair);
-  IncomingPasswordSharingInvitationSpecifics specifics;
-  specifics.set_encrypted_password_sharing_invitation_data(
-      std::string(encrypted_password.begin(), encrypted_password.end()));
-  specifics.set_guid(base::Uuid::GenerateRandomV4().AsLowercaseString());
-  specifics.set_recipient_key_version(recipient_public_key.version());
-
-  absl::optional<syncer::CrossUserSharingPublicKey> sender_public_key =
-      syncer::CrossUserSharingPublicKey::CreateByImport(
-          sender_key_pair.GetRawPublicKey());
-  DCHECK(sender_public_key);
-  sync_pb::UserInfo* sender_info = specifics.mutable_sender_info();
-  sender_info->mutable_cross_user_sharing_public_key()->CopyFrom(
-      PublicKeyToProto(sender_public_key.value()));
-  sender_info->mutable_user_display_info()->set_email(kSenderEmail);
-  sender_info->mutable_user_display_info()->set_display_name(
-      kSenderDisplayName);
-  sender_info->mutable_user_display_info()->set_profile_image_url(
-      kSenderProfileImageUrl);
-
-  return specifics;
+  return CreateEncryptedIncomingInvitationSpecifics(
+      CreateDefaultIncomingInvitation(kUsernameValue, kPasswordValue),
+      CreateDefaultSenderDisplayInfo(), recipient_public_key,
+      /*sender_key_pair=*/
+      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair());
 }
 
-// Waits for the incoming password to be stored locally.
-class PasswordStoredChecker : public SingleClientStatusChangeChecker {
- public:
-  PasswordStoredChecker(SyncServiceImpl* sync_service,
-                        PasswordStoreInterface* password_store,
-                        size_t expected_count)
-      : SingleClientStatusChangeChecker(sync_service),
-        password_store_(password_store),
-        expected_count_(expected_count) {}
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+// Fill in fields in `password_data` required for computing password client tag.
+// This is useful for testing collisions. Note that the `invitation` must
+// contain only one password in a group.
+void FillPasswordClientTagFromInvitation(
+    const sync_pb::PasswordSharingInvitationData& invitation_data,
+    sync_pb::PasswordSpecificsData* password_data) {
+  const sync_pb::PasswordSharingInvitationData::PasswordGroupData&
+      invitation_group_data = invitation_data.password_group_data();
+  CHECK_EQ(invitation_group_data.element_data().size(), 1);
+  const sync_pb::PasswordSharingInvitationData::PasswordGroupElementData&
+      invitation_group_element_data = invitation_group_data.element_data(0);
 
-  bool IsExitConditionSatisfied(std::ostream* os) override {
-    *os << "Waiting for " << expected_count_ << " passwords in the store. ";
-
-    size_t current_count = GetAllLogins(password_store_).size();
-    *os << "Current password count in the store: " << current_count;
-    return current_count == expected_count_;
-  }
-
- private:
-  const raw_ptr<PasswordStoreInterface> password_store_;
-  const size_t expected_count_;
-};
+  password_data->set_username_value(invitation_group_data.username_value());
+  password_data->set_origin(invitation_group_element_data.origin());
+  password_data->set_username_element(
+      invitation_group_element_data.username_element());
+  password_data->set_password_element(
+      invitation_group_element_data.password_element());
+  password_data->set_signon_realm(invitation_group_element_data.signon_realm());
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Waits until all the incoming invitations are deleted from the fake server.
 class ServerPasswordInvitationChecker
@@ -248,10 +157,12 @@
     return nigori_specifics.cross_user_sharing_public_key();
   }
 
-  void InjectInvitationToServer() {
+  void InjectInvitationToServer(
+      const sync_pb::IncomingPasswordSharingInvitationSpecifics&
+          invitation_specifics) {
     EntitySpecifics specifics;
     specifics.mutable_incoming_password_sharing_invitation()->CopyFrom(
-        CreateInvitationSpecifics(GetPublicKeyFromServer()));
+        invitation_specifics);
     GetFakeServer()->InjectEntity(
         syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
             /*non_unique_name=*/"",
@@ -260,21 +171,6 @@
             /*creation_time=*/0, /*last_modified_time=*/0));
   }
 
-  void InjectTestPasswordToFakeServer() {
-    sync_pb::PasswordSpecificsData password_data;
-    // Used for computing the client tag.
-    password_data.set_origin(kOrigin);
-    password_data.set_username_element(kUsernameElement);
-    password_data.set_username_value(kUsernameValue);
-    password_data.set_password_element(kPasswordElement);
-    password_data.set_signon_realm(kSignonRealm);
-    // Other data.
-    password_data.set_password_value(kLocalPasswordValue);
-
-    passwords_helper::InjectKeystoreEncryptedServerPassword(password_data,
-                                                            GetFakeServer());
-  }
-
   bool SetupSyncTransportWithoutPasswordAccountStorage() {
     if (!SetupClients()) {
       return false;
@@ -295,47 +191,68 @@
 IN_PROC_BROWSER_TEST_F(SingleClientIncomingPasswordSharingInvitationTest,
                        ShouldStoreIncomingPassword) {
   ASSERT_TRUE(SetupSync());
-  InjectInvitationToServer();
-  EXPECT_TRUE(PasswordStoredChecker(GetSyncService(0),
-                                    GetProfilePasswordStoreInterface(0),
-                                    /*expected_count=*/1)
-                  .Wait());
+
+  sync_pb::PasswordSharingInvitationData invitation_data =
+      CreateDefaultIncomingInvitation(kUsernameValue, kPasswordValue);
+  sync_pb::UserDisplayInfo sender_display_info =
+      CreateDefaultSenderDisplayInfo();
+
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetProfilePasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
+  InjectInvitationToServer(CreateEncryptedIncomingInvitationSpecifics(
+      invitation_data, sender_display_info,
+      /*recipient_public_key=*/GetPublicKeyFromServer(),
+      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair()));
+
+  EXPECT_TRUE(password_forms_added_checker.Wait());
   std::vector<std::unique_ptr<PasswordForm>> all_logins =
       GetAllLogins(GetProfilePasswordStoreInterface(0));
   ASSERT_EQ(1u, all_logins.size());
   const PasswordForm& password_form = *all_logins.front();
-  EXPECT_EQ(password_form.signon_realm, kSignonRealm);
-  EXPECT_EQ(password_form.url.spec(), kOrigin);
+  const sync_pb::PasswordSharingInvitationData::PasswordGroupData&
+      invitation_group_data = invitation_data.password_group_data();
+  const sync_pb::PasswordSharingInvitationData::PasswordGroupElementData&
+      invitation_group_element_data = invitation_group_data.element_data(0);
+  EXPECT_EQ(password_form.signon_realm,
+            invitation_group_element_data.signon_realm());
+  EXPECT_EQ(password_form.url.spec(), invitation_group_element_data.origin());
   EXPECT_EQ(base::UTF16ToUTF8(password_form.username_element),
-            kUsernameElement);
-  EXPECT_EQ(base::UTF16ToUTF8(password_form.username_value), kUsernameValue);
+            invitation_group_element_data.username_element());
+  EXPECT_EQ(base::UTF16ToUTF8(password_form.username_value),
+            invitation_group_data.username_value());
   EXPECT_EQ(base::UTF16ToUTF8(password_form.password_element),
-            kPasswordElement);
-  EXPECT_EQ(base::UTF16ToUTF8(password_form.password_value), kPasswordValue);
+            invitation_group_element_data.password_element());
+  EXPECT_EQ(base::UTF16ToUTF8(password_form.password_value),
+            invitation_group_data.password_value());
   EXPECT_EQ(base::UTF16ToUTF8(password_form.display_name),
-            kPasswordDisplayName);
-  EXPECT_EQ(password_form.icon_url.spec(), kPasswordAvatarUrl);
-  EXPECT_EQ(base::UTF16ToUTF8(password_form.sender_email), kSenderEmail);
-  EXPECT_EQ(base::UTF16ToUTF8(password_form.sender_name), kSenderDisplayName);
+            invitation_group_element_data.display_name());
+  EXPECT_EQ(password_form.icon_url.spec(),
+            invitation_group_element_data.avatar_url());
+
+  EXPECT_EQ(base::UTF16ToUTF8(password_form.sender_email),
+            sender_display_info.email());
+  EXPECT_EQ(base::UTF16ToUTF8(password_form.sender_name),
+            sender_display_info.display_name());
   EXPECT_EQ(password_form.sender_profile_image_url,
-            GURL(kSenderProfileImageUrl));
+            GURL(sender_display_info.profile_image_url()));
 }
 
 IN_PROC_BROWSER_TEST_F(SingleClientIncomingPasswordSharingInvitationTest,
                        ShouldIssueTombstoneAfterProcessingInvitation) {
   ASSERT_TRUE(SetupSync());
 
-  InjectInvitationToServer();
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetProfilePasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
+  InjectInvitationToServer(CreateInvitationSpecifics(GetPublicKeyFromServer()));
 
   // Wait the invitation to be processed and the password stored.
-  ASSERT_TRUE(PasswordStoredChecker(GetSyncService(0),
-                                    GetProfilePasswordStoreInterface(0),
-                                    /*expected_count=*/1)
-                  .Wait());
+  ASSERT_TRUE(password_forms_added_checker.Wait());
 
   // Check that all the invitations are eventually deleted from the server.
-  // PasswordStoredChecker above guarantees that there is an invitation present
-  // on the server (which was injected earlier).
+  // PasswordFormsAddedChecker above guarantees that there is an invitation
+  // present on the server (which was injected earlier).
   EXPECT_TRUE(ServerPasswordInvitationChecker(/*expected_count=*/0).Wait());
 }
 
@@ -351,14 +268,14 @@
   // Then stop sync service, inject an invitation to the server, and re-enable
   // sync again.
   GetClient(0)->SignOutPrimaryAccount();
-  InjectInvitationToServer();
+  InjectInvitationToServer(CreateInvitationSpecifics(GetPublicKeyFromServer()));
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetProfilePasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
   ASSERT_TRUE(GetClient(0)->SetupSync());
 
   // Wait the invitation to be processed and the password stored.
-  ASSERT_TRUE(PasswordStoredChecker(GetSyncService(0),
-                                    GetProfilePasswordStoreInterface(0),
-                                    /*expected_count=*/1)
-                  .Wait());
+  ASSERT_TRUE(password_forms_added_checker.Wait());
   EXPECT_THAT(GetAllLogins(GetProfilePasswordStoreInterface(0)),
               Contains(Pointee(
                   AllOf(Field(&PasswordForm::password_value,
@@ -373,6 +290,7 @@
 IN_PROC_BROWSER_TEST_F(
     SingleClientIncomingPasswordSharingInvitationTest,
     ShouldIgnoreIncomingInvitationIfPasswordExistsAtInitialSync) {
+  constexpr char kLocalPasswordValue[] = "local_password";
   base::HistogramTester histogram_tester;
 
   // First, setup sync to initialize Nigori node with a public key to be able to
@@ -380,18 +298,32 @@
   ASSERT_TRUE(SetupSync());
 
   // Then stop sync service, inject an invitation and a different password
-  // (but having the same client tag to cause a collision) to the server, and
-  // re-enable sync again.
+  // (but having the same client tag to cause a collision) to the server.
   GetClient(0)->SignOutPrimaryAccount();
-  InjectTestPasswordToFakeServer();
-  InjectInvitationToServer();
+
+  sync_pb::PasswordSharingInvitationData invitation_data =
+      CreateDefaultIncomingInvitation(kUsernameValue, kPasswordValue);
+  InjectInvitationToServer(CreateEncryptedIncomingInvitationSpecifics(
+      invitation_data, CreateDefaultSenderDisplayInfo(),
+      /*recipient_public_key=*/GetPublicKeyFromServer(),
+      /*sender_key_pair=*/
+      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair()));
+
+  sync_pb::PasswordSpecificsData password_data;
+  password_data.set_password_value(kLocalPasswordValue);
+  FillPasswordClientTagFromInvitation(invitation_data, &password_data);
+  passwords_helper::InjectKeystoreEncryptedServerPassword(password_data,
+                                                          GetFakeServer());
+
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetProfilePasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
+
+  // Re-enable sync again.
   ASSERT_TRUE(GetClient(0)->SetupSync());
 
   // Wait the password to be stored.
-  ASSERT_TRUE(PasswordStoredChecker(GetSyncService(0),
-                                    GetProfilePasswordStoreInterface(0),
-                                    /*expected_count=*/1)
-                  .Wait());
+  ASSERT_TRUE(password_forms_added_checker.Wait());
 
   // Verify that the invitation has been processed and a tombstone has been
   // uploaded.
@@ -436,11 +368,11 @@
   PasswordSyncActiveChecker(GetSyncService(0)).Wait();
   ASSERT_THAT(GetAllLogins(GetAccountPasswordStoreInterface(0)), IsEmpty());
 
-  InjectInvitationToServer();
-  EXPECT_TRUE(PasswordStoredChecker(GetSyncService(0),
-                                    GetAccountPasswordStoreInterface(0),
-                                    /*expected_count=*/1)
-                  .Wait());
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetAccountPasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
+  InjectInvitationToServer(CreateInvitationSpecifics(GetPublicKeyFromServer()));
+  EXPECT_TRUE(password_forms_added_checker.Wait());
   EXPECT_TRUE(ServerPasswordInvitationChecker(/*expected_count=*/0).Wait());
 
   EXPECT_THAT(GetAllLogins(GetProfilePasswordStoreInterface(0)), IsEmpty());
diff --git a/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc b/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
index f4b478f8..bcba764 100644
--- a/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_nigori_sync_test.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/sync/test/integration/bookmarks_helper.h"
 #include "chrome/browser/sync/test/integration/cookie_helper.h"
 #include "chrome/browser/sync/test/integration/encryption_helper.h"
+#include "chrome/browser/sync/test/integration/password_sharing_invitation_helper.h"
 #include "chrome/browser/sync/test/integration/passwords_helper.h"
 #include "chrome/browser/sync/test/integration/secondary_account_helper.h"
 #include "chrome/browser/sync/test/integration/single_client_status_change_checker.h"
@@ -46,6 +47,7 @@
 #include "components/sync/engine/nigori/cross_user_sharing_public_private_key_pair.h"
 #include "components/sync/engine/nigori/key_derivation_params.h"
 #include "components/sync/engine/nigori/nigori.h"
+#include "components/sync/nigori/cross_user_sharing_keys.h"
 #include "components/sync/nigori/cryptographer_impl.h"
 #include "components/sync/test/fake_server_nigori_helper.h"
 #include "components/sync/test/nigori_test_utils.h"
@@ -83,6 +85,10 @@
 
 using fake_server::GetServerNigori;
 using fake_server::SetNigoriInFakeServer;
+using password_sharing_helper::CreateDefaultIncomingInvitation;
+using password_sharing_helper::CreateDefaultSenderDisplayInfo;
+using password_sharing_helper::CreateEncryptedIncomingInvitationSpecifics;
+using passwords_helper::GetProfilePasswordStoreInterface;
 using syncer::BuildCustomPassphraseNigoriSpecifics;
 using syncer::BuildKeystoreNigoriSpecifics;
 using syncer::BuildTrustedVaultNigoriSpecifics;
@@ -94,6 +100,8 @@
 using testing::NotNull;
 using testing::SizeIs;
 
+constexpr int kKeyPairVersion = 0;
+
 MATCHER_P(IsDataEncryptedWith, key_params, "") {
   const sync_pb::EncryptedData& encrypted_data = arg;
   std::unique_ptr<syncer::Nigori> nigori = syncer::Nigori::CreateByDerivation(
@@ -136,6 +144,15 @@
       ->GetKeyName();
 }
 
+syncer::CrossUserSharingKeys GenerateNewKeyPair() {
+  syncer::CrossUserSharingKeys cross_user_sharing_keys =
+      syncer::CrossUserSharingKeys::CreateEmpty();
+  syncer::CrossUserSharingPublicPrivateKeyPair key_pair =
+      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair();
+  cross_user_sharing_keys.AddKeyPair(std::move(key_pair), kKeyPairVersion);
+  return cross_user_sharing_keys;
+}
+
 class WifiConfigurationsSyncActiveChecker
     : public SingleClientStatusChangeChecker {
  public:
@@ -287,8 +304,13 @@
     : public SingleClientNigoriSyncTest {
  public:
   SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest() {
-    override_features_.InitAndEnableFeature(
-        syncer::kSharingOfferKeyPairBootstrap);
+    // Enable the password receiving flow to verify cross-user sharing keys by
+    // decrypting incoming password sharing invitations.
+    override_features_.InitWithFeatures(
+        /*enabled_features=*/{syncer::kSharingOfferKeyPairBootstrap,
+                              password_manager::features::
+                                  kPasswordManagerEnableReceiverService},
+        /*disabled_features=*/{});
   }
 
   SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest(
@@ -301,6 +323,49 @@
   ~SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest() override =
       default;
 
+  void InjectInvitationToServer(
+      const sync_pb::IncomingPasswordSharingInvitationSpecifics&
+          invitation_specifics) {
+    sync_pb::EntitySpecifics specifics;
+    specifics.mutable_incoming_password_sharing_invitation()->CopyFrom(
+        invitation_specifics);
+    GetFakeServer()->InjectEntity(
+        syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
+            /*non_unique_name=*/"",
+            /*client_tag=*/
+            specifics.incoming_password_sharing_invitation().guid(), specifics,
+            /*creation_time=*/0, /*last_modified_time=*/0));
+  }
+
+  // Returns the current public key from the server.
+  sync_pb::CrossUserSharingPublicKey GetPublicKeyFromServer() const {
+    sync_pb::NigoriSpecifics nigori_specifics;
+    CHECK(fake_server::GetServerNigori(GetFakeServer(), &nigori_specifics));
+    CHECK(nigori_specifics.has_cross_user_sharing_public_key());
+    return nigori_specifics.cross_user_sharing_public_key();
+  }
+
+  void InjectNigoriWithCrossUserSharingKey(
+      const std::vector<uint8_t>& keystore_key,
+      const syncer::CrossUserSharingKeys& key_pair) {
+    const KeyParamsForTesting kDefaultKeyParams =
+        Pbkdf2PassphraseKeyParamsForTesting("password");
+    const KeyParamsForTesting keystore_key_params =
+        KeystoreKeyParamsForTesting(keystore_key);
+    SetNigoriInFakeServer(
+        BuildKeystoreNigoriSpecificsWithCrossUserSharingKeys(
+            /*keybag_keys_params=*/{kDefaultKeyParams, keystore_key_params},
+            /*keystore_decryptor_params*/ {kDefaultKeyParams},
+            /*keystore_key_params=*/keystore_key_params,
+            /*cross_user_sharing_keys=*/key_pair,
+            /*cross_user_sharing_public_key=*/
+            syncer::CrossUserSharingPublicKey::CreateByImport(
+                key_pair.GetKeyPair(kKeyPairVersion).GetRawPublicKey())
+                .value(),
+            /*cross_user_sharing_public_key_version=*/kKeyPairVersion),
+        GetFakeServer());
+  }
+
  private:
   base::test::ScopedFeatureList override_features_;
 };
@@ -736,6 +801,52 @@
 }
 
 IN_PROC_BROWSER_TEST_F(
+    SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTest,
+    ShouldPreferServerKeyPair) {
+  // Generates a local key pair and uploads it to the server.
+  ASSERT_TRUE(SetupSync());
+
+  sync_pb::NigoriSpecifics specifics;
+  ASSERT_TRUE(GetServerNigori(GetFakeServer(), &specifics));
+  ASSERT_TRUE(specifics.has_cross_user_sharing_public_key());
+
+  const std::vector<std::vector<uint8_t>>& keystore_keys =
+      GetFakeServer()->GetKeystoreKeys();
+  ASSERT_THAT(keystore_keys, SizeIs(1));
+  ASSERT_THAT(
+      specifics.encryption_keybag(),
+      IsDataEncryptedWith(KeystoreKeyParamsForTesting(keystore_keys.back())));
+
+  // Mimic server-side Nigori update by some other client. Current client should
+  // honor the server version of the key pair with the same version.
+  syncer::CrossUserSharingKeys new_key_pair = GenerateNewKeyPair();
+  InjectNigoriWithCrossUserSharingKey(keystore_keys.front(), new_key_pair);
+
+  // There is no easy way to wait for Cryptographer update to make it sure that
+  // the new key pair is propagated, so use bookmarks to verify that there was a
+  // sync cycle before testing password sharing.
+  // TODO(crbug.com/1511180): consider waiting for Cryptographer update rather
+  // than relying on bookmarks.
+  GetFakeServer()->InjectEntity(bookmarks_helper::CreateBookmarkServerEntity(
+      "title", GURL("http://abc.com")));
+  ASSERT_TRUE(bookmarks_helper::BookmarksTitleChecker(0, "title", 1).Wait());
+
+  // Add a new invitation encrypted using the new generated public key. The
+  // client should be able to decrypt this invitation.
+  PasswordFormsAddedChecker password_forms_added_checker(
+      GetProfilePasswordStoreInterface(0),
+      /*expected_new_password_forms=*/1);
+  InjectInvitationToServer(CreateEncryptedIncomingInvitationSpecifics(
+      CreateDefaultIncomingInvitation("username", "password"),
+      CreateDefaultSenderDisplayInfo(),
+      /*recipient_public_key=*/GetPublicKeyFromServer(),
+      syncer::CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair()));
+
+  // Wait the invitation to be processed and the password stored.
+  EXPECT_TRUE(password_forms_added_checker.Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(
     SingleClientNigoriCrossUserSharingPublicPrivateKeyPairSyncTestNoIpProt,
     PRE_ShouldSyncCrossUserSharingPublicPrivateKeyPair) {
   const std::vector<std::vector<uint8_t>>& keystore_keys =
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
index 8fa47393..6fc1619 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
@@ -514,6 +514,10 @@
   return location_bar_->GetLocationBarModel();
 }
 
+base::WeakPtr<OmniboxClient> ChromeOmniboxClient::AsWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 void ChromeOmniboxClient::DoPrerender(const AutocompleteMatch& match) {
   content::WebContents* web_contents = location_bar_->GetWebContents();
 
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.h b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
index 1b4abc7..db1e94d 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.h
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
@@ -23,7 +23,7 @@
 class LocationBar;
 class Profile;
 
-class ChromeOmniboxClient : public OmniboxClient {
+class ChromeOmniboxClient final : public OmniboxClient {
  public:
   ChromeOmniboxClient(LocationBar* location_bar,
                       Browser* browser,
@@ -106,6 +106,7 @@
       IDNA2008DeviationCharacter deviation_char_in_hostname) override;
   void OnInputInProgress(bool in_progress) override;
   void OnPopupVisibilityChanged() override;
+  base::WeakPtr<OmniboxClient> AsWeakPtr() override;
   LocationBarModel* GetLocationBarModel() override;
 
   // Update shortcuts when a navigation succeeds.
diff --git a/chrome/browser/ui/profiles/signin_intercept_first_run_experience_dialog.cc b/chrome/browser/ui/profiles/signin_intercept_first_run_experience_dialog.cc
index af85a4c..1aea686 100644
--- a/chrome/browser/ui/profiles/signin_intercept_first_run_experience_dialog.cc
+++ b/chrome/browser/ui/profiles/signin_intercept_first_run_experience_dialog.cc
@@ -317,7 +317,6 @@
 
   // TurnSyncOnHelper deletes itself once done.
   new TurnSyncOnHelper(browser_->profile(), access_point, promo_action,
-                       signin_metrics::Reason::kSigninPrimaryAccount,
                        account_id_, abort_mode,
                        std::make_unique<InterceptTurnSyncOnHelperDelegate>(
                            weak_ptr_factory_.GetWeakPtr()),
diff --git a/chrome/browser/ui/startup/silent_sync_enabler.cc b/chrome/browser/ui/startup/silent_sync_enabler.cc
index 229b654..2b67f2e0 100644
--- a/chrome/browser/ui/startup/silent_sync_enabler.cc
+++ b/chrome/browser/ui/startup/silent_sync_enabler.cc
@@ -108,8 +108,7 @@
   // TurnSyncOnHelper deletes itself once done.
   new TurnSyncOnHelper(
       &profile_.get(), signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
-      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kSigninPrimaryAccount, account_id,
+      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO, account_id,
       TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT,
       std::make_unique<SilentTurnSyncOnHelperDelegate>(), std::move(callback_));
 }
diff --git a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
index 79c6311e..5c5d1f2 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_cell_utils.cc
@@ -55,6 +55,7 @@
 
 // The additional height of the row in case it has two lines of text.
 constexpr int kAutofillPopupAdditionalDoubleRowHeight = 22;
+constexpr int kAutofillPopupAdditionalDoubleRowHeightNewStyle = 16;
 
 // The additional padding of the row in case it has three lines of text.
 constexpr int kAutofillPopupAdditionalPadding = 16;
@@ -154,11 +155,16 @@
     case Suggestion::Icon::kKey:
       return ImageViewFromVectorIcon(kKeyIcon, kIconSize);
     case Suggestion::Icon::kEdit:
-      return ImageViewFromVectorIcon(vector_icons::kEditIcon, kIconSize);
+      return ImageViewFromVectorIcon(vector_icons::kEditChromeRefreshIcon,
+                                     kIconSize);
     case Suggestion::Icon::kCode:
       return ImageViewFromVectorIcon(vector_icons::kCodeIcon, kIconSize);
     case Suggestion::Icon::kLocation:
-      return ImageViewFromVectorIcon(vector_icons::kLocationOnIcon, kIconSize);
+      return ImageViewFromVectorIcon(
+          ShouldApplyNewAutofillPopupStyle()
+              ? vector_icons::kLocationOnChromeRefreshIcon
+              : vector_icons::kLocationOnIcon,
+          kIconSize);
     case Suggestion::Icon::kDelete:
       return ImageViewFromVectorIcon(kTrashCanLightIcon, kIconSize);
     case Suggestion::Icon::kClear:
@@ -430,13 +436,14 @@
   layout.set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
 
-  // Adjust the cell height based on the number of subtexts.
-  const int kStandardRowHeight =
-      views::MenuConfig::instance().touchable_menu_height;
-  const int kActualHeight =
-      kStandardRowHeight +
-      (subtext_views.empty() ? 0 : kAutofillPopupAdditionalDoubleRowHeight);
-  layout.set_minimum_cross_axis_size(kActualHeight);
+  // Adjust the row height based on the number of subtexts (lines of text).
+  int row_height = views::MenuConfig::instance().touchable_menu_height;
+  if (!subtext_views.empty()) {
+    row_height += ShouldApplyNewAutofillPopupStyle()
+                      ? kAutofillPopupAdditionalDoubleRowHeightNewStyle
+                      : kAutofillPopupAdditionalDoubleRowHeight;
+  }
+  layout.set_minimum_cross_axis_size(row_height);
 
   // If there are three rows in total, add extra padding to avoid cramming.
   DCHECK_LE(subtext_views.size(), 2u);
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views.cc b/chrome/browser/ui/views/autofill/popup/popup_view_views.cc
index 1e00686..209f8728 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_view_views.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_view_views.cc
@@ -38,6 +38,7 @@
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
 #include "components/autofill/core/browser/ui/popup_types.h"
 #include "components/autofill/core/browser/ui/suggestion.h"
+#include "components/autofill/core/common/aliases.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/feature_engagement/public/feature_constants.h"
@@ -235,43 +236,7 @@
 
 void PopupViewViews::SetSelectedCell(std::optional<CellIndex> cell_index,
                                      PopupCellSelectionSource source) {
-  std::optional<CellIndex> old_index = GetSelectedCell();
-  if (old_index == cell_index) {
-    return;
-  }
-
-  if (old_index) {
-    GetPopupRowViewAt(old_index->first).SetSelectedCell(std::nullopt);
-  }
-
-  if (open_sub_popup_timer_.IsRunning()) {
-    open_sub_popup_timer_.Stop();
-  }
-
-  if (cell_index && HasPopupRowViewAt(cell_index->first)) {
-    has_keyboard_focus_ = true;
-
-    row_with_selected_cell_ = cell_index->first;
-    PopupRowView& new_row = GetPopupRowViewAt(cell_index->first);
-    new_row.SetSelectedCell(cell_index->second);
-    new_row.ScrollViewToVisible();
-
-    bool can_open_sub_popup =
-        cell_index->second == PopupRowView::CellType::kControl &&
-        !controller_->GetSuggestionAt(cell_index->first).children.empty();
-    std::optional<size_t> row_with_open_sub_popup =
-        can_open_sub_popup ? std::optional(cell_index->first) : std::nullopt;
-    base::TimeDelta delay = source == PopupCellSelectionSource::kMouse
-                                ? kMouseOpenSubPopupDelay
-                                : kNonMouseOpenSubPopupDelay;
-    open_sub_popup_timer_.Start(
-        FROM_HERE, delay,
-        base::BindOnce(&PopupViewViews::SetRowWithOpenSubPopup,
-                       weak_ptr_factory_.GetWeakPtr(), row_with_open_sub_popup,
-                       source));
-  } else {
-    row_with_selected_cell_ = std::nullopt;
-  }
+  SetSelectedCell(cell_index, source, AutoselectFirstSuggestion(false));
 }
 
 bool PopupViewViews::HandleKeyPressEvent(
@@ -430,7 +395,7 @@
         row.GetExpandChildSuggestionsView()) {
       SetSelectedCell(
           CellIndex{selected_cell->first, PopupRowView::CellType::kControl},
-          PopupCellSelectionSource::kKeyboard);
+          PopupCellSelectionSource::kKeyboard, AutoselectFirstSuggestion(true));
       return true;
     }
   }
@@ -494,7 +459,7 @@
   if (open_sub_popup_timer_.IsRunning()) {
     open_sub_popup_timer_.Stop();
   }
-  SetRowWithOpenSubPopup(std::nullopt, PopupCellSelectionSource::kNonUserInput);
+  SetRowWithOpenSubPopup(std::nullopt);
 
   CreateChildViews();
   DoUpdateBoundsAndRedrawPopup();
@@ -590,6 +555,52 @@
       feature_engagement::kIPHAutofillExternalAccountProfileSuggestionFeature);
 }
 
+void PopupViewViews::SetSelectedCell(
+    std::optional<CellIndex> cell_index,
+    PopupCellSelectionSource source,
+    AutoselectFirstSuggestion autoselect_first_suggestion) {
+  std::optional<CellIndex> old_index = GetSelectedCell();
+  if (old_index == cell_index) {
+    return;
+  }
+
+  if (old_index) {
+    GetPopupRowViewAt(old_index->first).SetSelectedCell(std::nullopt);
+  }
+
+  if (open_sub_popup_timer_.IsRunning()) {
+    open_sub_popup_timer_.Stop();
+  }
+
+  if (cell_index && HasPopupRowViewAt(cell_index->first)) {
+    has_keyboard_focus_ = true;
+
+    row_with_selected_cell_ = cell_index->first;
+    PopupRowView& new_selected_row = GetPopupRowViewAt(cell_index->first);
+    new_selected_row.SetSelectedCell(cell_index->second);
+    new_selected_row.ScrollViewToVisible();
+
+    bool can_open_sub_popup =
+        cell_index->second == PopupRowView::CellType::kControl;
+
+    CHECK(!can_open_sub_popup ||
+          !controller_->GetSuggestionAt(cell_index->first).children.empty());
+
+    std::optional<size_t> row_with_open_sub_popup =
+        can_open_sub_popup ? std::optional(cell_index->first) : std::nullopt;
+    base::TimeDelta delay = source == PopupCellSelectionSource::kMouse
+                                ? kMouseOpenSubPopupDelay
+                                : kNonMouseOpenSubPopupDelay;
+    open_sub_popup_timer_.Start(
+        FROM_HERE, delay,
+        base::BindOnce(&PopupViewViews::SetRowWithOpenSubPopup,
+                       weak_ptr_factory_.GetWeakPtr(), row_with_open_sub_popup,
+                       autoselect_first_suggestion));
+  } else {
+    row_with_selected_cell_ = std::nullopt;
+  }
+}
+
 bool PopupViewViews::HasPopupRowViewAt(size_t index) const {
   return index < rows_.size() &&
          absl::holds_alternative<PopupRowView*>(rows_[index]);
@@ -888,7 +899,7 @@
       FROM_HERE, kNoSelectionHideSubPopupDelay,
       base::BindRepeating(&PopupViewViews::SetRowWithOpenSubPopup,
                           weak_ptr_factory_.GetWeakPtr(), std::nullopt,
-                          PopupCellSelectionSource::kNonUserInput));
+                          AutoselectFirstSuggestion(false)));
 }
 
 bool PopupViewViews::CanShowDropdownInBounds(const gfx::Rect& bounds) const {
@@ -911,7 +922,7 @@
 
 void PopupViewViews::SetRowWithOpenSubPopup(
     std::optional<size_t> row_index,
-    PopupCellSelectionSource selection_source) {
+    AutoselectFirstSuggestion autoselect_first_suggestion) {
   if (row_with_open_sub_popup_ == row_index) {
     return;
   }
@@ -932,13 +943,12 @@
     CHECK(!suggestion.children.empty());
 
     PopupRowView& row = GetPopupRowViewAt(*row_index);
-    if (controller_->OpenSubPopup(
-            row.GetControlCellBounds(), suggestion.children,
-            AutoselectFirstSuggestion(selection_source ==
-                                      PopupCellSelectionSource::kKeyboard))) {
+    if (controller_->OpenSubPopup(row.GetControlCellBounds(),
+                                  suggestion.children,
+                                  autoselect_first_suggestion)) {
       row.SetChildSuggestionsDisplayed(true);
       row_with_open_sub_popup_ = row_index;
-      if (selection_source == PopupCellSelectionSource::kKeyboard) {
+      if (autoselect_first_suggestion) {
         row.SetSelectedCell(std::nullopt);
       }
     }
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views.h b/chrome/browser/ui/views/autofill/popup/popup_view_views.h
index b19f1e8..15c59c0 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_view_views.h
+++ b/chrome/browser/ui/views/autofill/popup/popup_view_views.h
@@ -111,6 +111,10 @@
  private:
   friend class PopupViewViewsTestApi;
 
+  void SetSelectedCell(std::optional<CellIndex> cell_index,
+                       PopupCellSelectionSource source,
+                       AutoselectFirstSuggestion autoselect_first_suggestion);
+
   // Returns the `PopupRowView` at line number `index`. Assumes that there is
   // such a view at that line number - otherwise the underlying variant will
   // check false.
@@ -181,8 +185,10 @@
 
   // Opens a sub-popup on a new row (and closes the open one if any), or just
   // closes the existing if `std::nullopt` is passed.
-  void SetRowWithOpenSubPopup(std::optional<size_t> row_index,
-                              PopupCellSelectionSource selection_source);
+  void SetRowWithOpenSubPopup(
+      std::optional<size_t> row_index,
+      AutoselectFirstSuggestion autoselect_first_suggestion =
+          AutoselectFirstSuggestion(false));
 
   // Controller for this view.
   base::WeakPtr<AutofillPopupController> controller_ = nullptr;
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc
index 640bb02..9eab6b0 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc
@@ -945,19 +945,28 @@
       << "The cell's sub-popup should be closed.";
 }
 
-TEST_F(PopupViewViewsTest, NoSubPopupOpenIfNotEligible) {
-  controller().set_suggestions({
+// `PopupViewViewsTest` is not used in death tests because it sets up a complex
+// environment (namely creates a `TestingProfile`) that fails to be created in
+// the sub-process (see `ASSERT_DEATH` doc for details). This fail hides
+// the real death reason to be tested.
+using PopupViewViewsDeathTest = ChromeViewsTestBase;
+TEST_F(PopupViewViewsDeathTest, OpenSubPopupWithNoChildrenCheckCrash) {
+  NiceMock<MockAutofillPopupController> controller;
+  controller.set_suggestions({
       // Regular suggestion with no children,
       Suggestion(u"Suggestion #1"),
       Suggestion(u"Suggestion #2"),
   });
-  CreateAndShowView();
+  std::unique_ptr<views::Widget> widget = CreateTestWidget();
+  std::unique_ptr<PopupViewViews> view =
+      std::make_unique<PopupViewViews>(controller.GetWeakPtr());
+  raw_ptr<PopupViewViews> view_ptr = widget->SetContentsView(std::move(view));
+  view_ptr->Show(AutoselectFirstSuggestion(false));
 
-  view().SetSelectedCell(CellIndex{0, CellType::kControl},
-                         PopupCellSelectionSource::kNonUserInput);
-  task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
-  EXPECT_EQ(test_api(view()).GetOpenSubPopupRow(), std::nullopt)
-      << "Opening a sub-popup should happen.";
+  ASSERT_DEATH(
+      view_ptr->SetSelectedCell(CellIndex{0, CellType::kControl},
+                                PopupCellSelectionSource::kNonUserInput),
+      "can_open_sub_popup");
 }
 
 TEST_F(PopupViewViewsTest, SubPopupHidingOnNoSelection) {
@@ -1041,6 +1050,34 @@
   EXPECT_EQ(test_api(*sub_view).GetOpenSubPopupRow(), std::nullopt);
 }
 
+TEST_F(PopupViewViewsTest, SubPopupOpensWithNoAutoselectByMouse) {
+  controller().set_suggestions({
+      CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
+  });
+  CreateAndShowView();
+
+  EXPECT_CALL(controller(),
+              OpenSubPopup(_, _, AutoselectFirstSuggestion(false)));
+
+  view().SetSelectedCell(CellIndex{0, CellType::kControl},
+                         PopupCellSelectionSource::kMouse);
+  task_environment()->FastForwardBy(PopupViewViews::kMouseOpenSubPopupDelay);
+}
+
+TEST_F(PopupViewViewsTest, SubPopupOpensWithAutoselectByRightKey) {
+  controller().set_suggestions({
+      CreateSuggestionWithChildren({Suggestion(u"Child #1")}),
+  });
+  CreateAndShowView();
+
+  EXPECT_CALL(controller(),
+              OpenSubPopup(_, _, AutoselectFirstSuggestion(true)));
+
+  SimulateKeyPress(ui::VKEY_DOWN);
+  SimulateKeyPress(ui::VKEY_RIGHT);
+  task_environment()->FastForwardBy(PopupViewViews::kNonMouseOpenSubPopupDelay);
+}
+
 // TODO(crbug.com/1489673): Enable once the view shows itself properly.
 #if !BUILDFLAG(IS_MAC)
 // Tests that `GetPopupScreenLocation` returns the bounds and arrow position of
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 312f2e9..953b201 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -2608,11 +2608,8 @@
 }
 
 void BrowserView::OnWidgetShowStateChanged(views::Widget* widget) {
-// TODO(crbug.com/1503145): Investigate and fix on MacOS.
-#if !BUILDFLAG(IS_MAC)
   // `display-state` @media feature value in renderer needs to be updated.
   SynchronizeRenderWidgetHostVisualPropertiesForMainFrame();
-#endif
 }
 
 void BrowserView::PrimaryPageChanged(content::Page& page) {
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
index 60ca5cb..e20f263 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
@@ -486,6 +486,7 @@
   }
 
   void ClickEnableLiveCaptionOnDialog() {
+    base::RunLoop().RunUntilIdle();
     base::RunLoop run_loop;
     PrefChangeRegistrar change_observer;
     change_observer.Init(browser()->profile()->GetPrefs());
@@ -499,6 +500,7 @@
   }
 
   void ClickEnableLiveTranslateOnDialog() {
+    base::RunLoop().RunUntilIdle();
     base::RunLoop run_loop;
     PrefChangeRegistrar change_observer;
     change_observer.Init(browser()->profile()->GetPrefs());
diff --git a/chrome/browser/ui/views/payments/payment_method_view_controller.cc b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
index dd733e1..09df96c 100644
--- a/chrome/browser/ui/views/payments/payment_method_view_controller.cc
+++ b/chrome/browser/ui/views/payments/payment_method_view_controller.cc
@@ -12,6 +12,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/ui/views/chrome_typography.h"
 #include "chrome/browser/ui/views/payments/payment_request_dialog_view.h"
@@ -40,7 +41,7 @@
 
 namespace {
 
-class PaymentMethodListItem : public PaymentRequestItemList::Item {
+class PaymentMethodListItem final : public PaymentRequestItemList::Item {
  public:
   // Does not take ownership of |app|, which should not be null and should
   // outlive this object. |list| is the PaymentRequestItemList object that will
@@ -67,6 +68,10 @@
 
   ~PaymentMethodListItem() override {}
 
+  base::WeakPtr<PaymentRequestRowView> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   // PaymentRequestItemList::Item:
   std::unique_ptr<views::View> CreateExtraView() override {
@@ -139,6 +144,7 @@
 
   base::WeakPtr<PaymentApp> app_;
   base::WeakPtr<PaymentRequestDialogView> dialog_;
+  base::WeakPtrFactory<PaymentMethodListItem> weak_ptr_factory_{this};
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/payments/payment_request_item_list.h b/chrome/browser/ui/views/payments/payment_request_item_list.h
index df246cb..e3a9ef3 100644
--- a/chrome/browser/ui/views/payments/payment_request_item_list.h
+++ b/chrome/browser/ui/views/payments/payment_request_item_list.h
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/views/payments/payment_request_row_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
@@ -63,6 +64,10 @@
     base::WeakPtr<PaymentRequestSpec> spec() { return spec_; }
     base::WeakPtr<PaymentRequestState> state() { return state_; }
 
+    // PaymentRequestRowView overrides
+    // Leaf classes must override this and provide their own factory.
+    base::WeakPtr<PaymentRequestRowView> AsWeakPtr() override = 0;
+
    protected:
     // Initializes the layout and content of the row. Must be called by subclass
     // constructors, so that virtual methods providing row contents are
diff --git a/chrome/browser/ui/views/payments/payment_request_item_list_unittest.cc b/chrome/browser/ui/views/payments/payment_request_item_list_unittest.cc
index 71d9ed9..0da33d2 100644
--- a/chrome/browser/ui/views/payments/payment_request_item_list_unittest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_item_list_unittest.cc
@@ -8,6 +8,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/memory/weak_ptr.h"
 #include "components/payments/content/payment_request_spec.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
@@ -33,8 +34,7 @@
                                      list,
                                      selected,
                                      /*clickable=*/true,
-                                     /*show_edit_button=*/false),
-        selected_state_changed_calls_count_(0) {
+                                     /*show_edit_button=*/false) {
     Init();
   }
 
@@ -45,6 +45,10 @@
     return selected_state_changed_calls_count_;
   }
 
+  base::WeakPtr<PaymentRequestRowView> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   std::unique_ptr<views::View> CreateContentView(
       std::u16string* accessible_content) override {
@@ -63,7 +67,8 @@
     ++selected_state_changed_calls_count_;
   }
 
-  int selected_state_changed_calls_count_;
+  int selected_state_changed_calls_count_ = 0;
+  base::WeakPtrFactory<TestListItem> weak_ptr_factory_{this};
 };
 
 }  // namespace
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 2527577..abb43a8 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.cc
@@ -80,6 +80,10 @@
   OnPropertyChanged(&clickable_, views::PropertyEffects::kPropertyEffectsPaint);
 }
 
+base::WeakPtr<PaymentRequestRowView> PaymentRequestRowView::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 gfx::Insets PaymentRequestRowView::GetRowInsets() const {
   return row_insets_;
 }
diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.h b/chrome/browser/ui/views/payments/payment_request_row_view.h
index 57d585b..025cafde 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.h
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.h
@@ -14,9 +14,7 @@
 
 // This class implements a clickable row of the Payment Request dialog that
 // darkens on hover and displays a horizontal ruler on its lower bound.
-class PaymentRequestRowView
-    : public views::Button,
-      public base::SupportsWeakPtr<PaymentRequestRowView> {
+class PaymentRequestRowView : public views::Button {
  public:
   METADATA_HEADER(PaymentRequestRowView);
   PaymentRequestRowView();
@@ -39,6 +37,9 @@
     previous_row_ = previous_row;
   }
 
+  // Deriving classes must override this and provide their own factory.
+  virtual base::WeakPtr<PaymentRequestRowView> AsWeakPtr();
+
  private:
   // Show/hide the separator at the bottom of the row. This is used to hide the
   // separator when the row is hovered.
@@ -71,6 +72,8 @@
   // A non-owned pointer to the previous row object in the UI. Used to hide the
   // bottom border of the previous row when highlighting this one. May be null.
   base::WeakPtr<PaymentRequestRowView> previous_row_;
+
+  base::WeakPtrFactory<PaymentRequestRowView> weak_ptr_factory_{this};
 };
 
 BEGIN_VIEW_BUILDER(, PaymentRequestRowView, views::Button)
diff --git a/chrome/browser/ui/views/payments/profile_list_view_controller.cc b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
index 0044b18..d68c323 100644
--- a/chrome/browser/ui/views/payments/profile_list_view_controller.cc
+++ b/chrome/browser/ui/views/payments/profile_list_view_controller.cc
@@ -65,6 +65,10 @@
 
   ~ProfileItem() override {}
 
+  base::WeakPtr<PaymentRequestRowView> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   // PaymentRequestItemList::Item:
   std::unique_ptr<views::View> CreateContentView(
@@ -102,6 +106,7 @@
 
   base::WeakPtr<ProfileListViewController> controller_;
   raw_ptr<autofill::AutofillProfile> profile_;
+  base::WeakPtrFactory<ProfileItem> weak_ptr_factory_{this};
 };
 
 // The ProfileListViewController subtype for the Shipping address list
diff --git a/chrome/browser/ui/views/payments/shipping_option_view_controller.cc b/chrome/browser/ui/views/payments/shipping_option_view_controller.cc
index 24bc095..89e4ca68 100644
--- a/chrome/browser/ui/views/payments/shipping_option_view_controller.cc
+++ b/chrome/browser/ui/views/payments/shipping_option_view_controller.cc
@@ -21,7 +21,7 @@
 
 namespace {
 
-class ShippingOptionItem : public PaymentRequestItemList::Item {
+class ShippingOptionItem final : public PaymentRequestItemList::Item {
  public:
   ShippingOptionItem(mojom::PaymentShippingOptionPtr shipping_option,
                      base::WeakPtr<PaymentRequestSpec> spec,
@@ -44,6 +44,10 @@
 
   ~ShippingOptionItem() override {}
 
+  base::WeakPtr<PaymentRequestRowView> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   // payments::PaymentRequestItemList::Item:
   std::unique_ptr<views::View> CreateContentView(
@@ -82,6 +86,7 @@
   }
 
   mojom::PaymentShippingOptionPtr shipping_option_;
+  base::WeakPtrFactory<ShippingOptionItem> weak_ptr_factory_{this};
 };
 
 }  // namespace
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index 5df321a..7a9b30f 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -687,7 +687,6 @@
                 (Profile * profile,
                  signin_metrics::AccessPoint access_point,
                  signin_metrics::PromoAction promo_action,
-                 signin_metrics::Reason signin_reason,
                  const CoreAccountId& account_id,
                  TurnSyncOnHelper::SigninAbortedMode signin_aborted_mode),
                 ());
@@ -762,7 +761,7 @@
           browser()->profile(),
           signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN,
           signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT,
-          signin_metrics::Reason::kReauthentication, account_info().account_id,
+          account_info().account_id,
           TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT))
       .WillOnce([&loop]() { loop.Quit(); });
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.cc b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.cc
index 19735abf..d529bbd 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.cc
@@ -262,7 +262,6 @@
     Profile* profile,
     signin_metrics::AccessPoint /*access_point*/,
     signin_metrics::PromoAction /*promo_action*/,
-    signin_metrics::Reason /*reason*/,
     content::WebContents* /*contents*/,
     const CoreAccountInfo& account_info) {
   CHECK_EQ(profile, profile_.get());
diff --git a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.h b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.h
index 207cd44..0b6d9604 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.h
+++ b/chrome/browser/ui/views/profiles/profile_picker_dice_sign_in_provider.h
@@ -114,7 +114,6 @@
   void FinishFlowInPicker(Profile* profile,
                           signin_metrics::AccessPoint access_point,
                           signin_metrics::PromoAction promo_action,
-                          signin_metrics::Reason reason,
                           content::WebContents* contents,
                           const CoreAccountInfo& account_info);
 
diff --git a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc
index 292dd9e..398a3b9 100644
--- a/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc
+++ b/chrome/browser/ui/views/profiles/profile_picker_signed_in_flow_controller.cc
@@ -69,9 +69,6 @@
   new TurnSyncOnHelper(
       profile_, signin_access_point_,
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_util::IsForceSigninEnabled()
-          ? signin_metrics::Reason::kForcedSigninPrimaryAccount
-          : signin_metrics::Reason::kSigninPrimaryAccount,
       account_info.account_id,
       TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT,
       std::make_unique<ProfilePickerTurnSyncOnDelegate>(
diff --git a/chrome/browser/ui/views/side_panel/side_panel.cc b/chrome/browser/ui/views/side_panel/side_panel.cc
index b6c185c..29717df5 100644
--- a/chrome/browser/ui/views/side_panel/side_panel.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel.cc
@@ -106,32 +106,30 @@
     canvas->sk_canvas()->clipRRect(rect, SkClipOp::kDifference,
                                    /*do_anti_alias=*/true);
 
-    const SkScalar scaled_border_radii[8] = {
-        border_radii_.upper_left() * dsf,  border_radii_.upper_left() * dsf,
-        border_radii_.upper_right() * dsf, border_radii_.upper_right() * dsf,
-        border_radii_.lower_right() * dsf, border_radii_.lower_right() * dsf,
-        border_radii_.lower_left() * dsf,  border_radii_.lower_left() * dsf};
-
-    // Use ToEnclosedRect to make sure that `rounded_border_path` never end up
-    // larger than the view bounds.
-    const gfx::Rect scaled_view_bounds = ToEnclosedRect(scaled_view_bounds_f);
-
-    SkPath rounded_border_path;
-    rounded_border_path.addRoundRect(gfx::RectToSkRect(scaled_view_bounds),
-                                     scaled_border_radii, SkPathDirection::kCW);
-
-    // Add another clip to the canvas that rounds the outer corners of the
-    // border.
-    canvas->ClipPath(rounded_border_path, /*do_anti_alias=*/true);
-
-    // Draw the top-container background.
     {
-      // Redo device-scale factor, the theme background is drawn in DIPs. Note
-      // that the clip area above is in pixels, hence the
-      // UndoDeviceScaleFactor() call before this.
+      // Redo the device scale factor. The theme background and clip for the
+      // outer corners are drawn in DIPs. Note that the clip area above is in
+      // pixels because `UndoDeviceScaleFactor()` was called before this.
       gfx::ScopedCanvas scoped_rescale(canvas);
       canvas->Scale(dsf, dsf);
 
+      const SkScalar border_radii[8] = {
+          border_radii_.upper_left(),  border_radii_.upper_left(),
+          border_radii_.upper_right(), border_radii_.upper_right(),
+          border_radii_.lower_right(), border_radii_.lower_right(),
+          border_radii_.lower_left(),  border_radii_.lower_left()};
+
+      SkPath rounded_border_path;
+      rounded_border_path.addRoundRect(gfx::RectToSkRect(view.GetLocalBounds()),
+                                       border_radii, SkPathDirection::kCW);
+
+      // Add another clip to the canvas that rounds the outer corners of the
+      // border. This is done in DIPs because for some device scale factors, the
+      // conversion to pixels can cause the clip to be off by a pixel, resulting
+      // in a pixel gap between the side panel border and web contents.
+      canvas->ClipPath(rounded_border_path, /*do_anti_alias=*/true);
+
+      // Draw the top-container background.
       TopContainerBackground::PaintBackground(canvas, &view, browser_view_);
     }
 
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index 9310104..b1d1d3aa 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -8,6 +8,7 @@
 #include "base/base64url.h"
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
+#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
@@ -453,7 +454,7 @@
 
 // TODO(crbug.com/1431513): Consider inheriting from `ChromeOmniboxClient`
 //  to avoid reimplementation of methods like `OnBookmarkLaunched`.
-class RealboxOmniboxClient : public OmniboxClient {
+class RealboxOmniboxClient final : public OmniboxClient {
  public:
   RealboxOmniboxClient(LocationBarModel* location_bar_model,
                        Profile* profile,
@@ -495,12 +496,14 @@
       const AutocompleteMatch& alternative_nav_match,
       IDNA2008DeviationCharacter deviation_char_in_hostname) override;
   LocationBarModel* GetLocationBarModel() override;
+  base::WeakPtr<OmniboxClient> AsWeakPtr() override;
 
  private:
   raw_ptr<LocationBarModel> location_bar_model_;
   raw_ptr<Profile> profile_;
   raw_ptr<content::WebContents> web_contents_;
   ChromeAutocompleteSchemeClassifier scheme_classifier_;
+  base::WeakPtrFactory<RealboxOmniboxClient> weak_factory_{this};
 };
 
 RealboxOmniboxClient::RealboxOmniboxClient(LocationBarModel* location_bar_model,
@@ -611,6 +614,10 @@
   return location_bar_model_;
 }
 
+base::WeakPtr<OmniboxClient> RealboxOmniboxClient::AsWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 }  // namespace
 
 // static
diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
index 80012ef..062d43a 100644
--- a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
+++ b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc
@@ -397,8 +397,6 @@
         signin_metrics::AccessPoint::ACCESS_POINT_FORCED_SIGNIN,
         signin_metrics::SourceForRefreshTokenOperation::
             kInlineLoginHandler_Signin);
-
-    signin_metrics::LogSigninReason(signin_metrics::Reason::kReauthentication);
   } else {
     if (confirm_untrusted_signin_) {
       // Display a confirmation dialog to the user.
@@ -462,8 +460,7 @@
 
   new TurnSyncOnHelper(
       profile_, signin::GetAccessPointForEmbeddedPromoURL(current_url_),
-      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin::GetSigninReasonForEmbeddedPromoURL(current_url_), account_id,
+      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO, account_id,
       TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT, std::move(delegate),
       base::BindOnce(&OnSigninComplete, profile_, email_, password_,
                      is_force_sign_in_with_usermanager_));
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
index 6198ff9c..232820c 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.cc
@@ -194,7 +194,6 @@
     Profile* profile,
     signin_metrics::AccessPoint signin_access_point,
     signin_metrics::PromoAction signin_promo_action,
-    signin_metrics::Reason signin_reason,
     const CoreAccountId& account_id,
     SigninAbortedMode signin_aborted_mode,
     std::unique_ptr<Delegate> delegate,
@@ -204,7 +203,6 @@
       identity_manager_(IdentityManagerFactory::GetForProfile(profile)),
       signin_access_point_(signin_access_point),
       signin_promo_action_(signin_promo_action),
-      signin_reason_(signin_reason),
       signin_aborted_mode_(signin_aborted_mode),
       account_info_(
           identity_manager_->FindExtendedAccountInfoByAccountId(account_id)),
@@ -237,13 +235,11 @@
     Browser* browser,
     signin_metrics::AccessPoint signin_access_point,
     signin_metrics::PromoAction signin_promo_action,
-    signin_metrics::Reason signin_reason,
     const CoreAccountId& account_id,
     SigninAbortedMode signin_aborted_mode)
     : TurnSyncOnHelper(profile,
                        signin_access_point,
                        signin_promo_action,
-                       signin_reason,
                        account_id,
                        signin_aborted_mode,
                        std::make_unique<TurnSyncOnHelperDelegateImpl>(browser),
@@ -500,10 +496,8 @@
                                              signin_access_point_);
   // If the account is already signed in, `SetPrimaryAccount()` above is a no-op
   // and the logs below are inaccurate.
-  // TODO(crbug.com/1402935): Review and rebuild the SigninReason logging.
   signin_metrics::LogSigninAccessPointCompleted(signin_access_point_,
                                                 signin_promo_action_);
-  signin_metrics::LogSigninReason(signin_reason_);
   base::RecordAction(base::UserMetricsAction("Signin_Signin_Succeed"));
 
   bool user_accepted_management =
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
index 590a55c..f945ad5f 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper.h
@@ -140,7 +140,6 @@
   TurnSyncOnHelper(Profile* profile,
                    signin_metrics::AccessPoint signin_access_point,
                    signin_metrics::PromoAction signin_promo_action,
-                   signin_metrics::Reason signin_reason,
                    const CoreAccountId& account_id,
                    SigninAbortedMode signin_aborted_mode,
                    std::unique_ptr<Delegate> delegate,
@@ -151,7 +150,6 @@
                    Browser* browser,
                    signin_metrics::AccessPoint signin_access_point,
                    signin_metrics::PromoAction signin_promo_action,
-                   signin_metrics::Reason signin_reason,
                    const CoreAccountId& account_id,
                    SigninAbortedMode signin_aborted_mode);
 
@@ -252,7 +250,6 @@
   raw_ptr<signin::IdentityManager> identity_manager_;
   const signin_metrics::AccessPoint signin_access_point_;
   const signin_metrics::PromoAction signin_promo_action_;
-  const signin_metrics::Reason signin_reason_;
 
   // Whether the refresh token should be deleted if the Sync flow is aborted.
   SigninAbortedMode signin_aborted_mode_;
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
index 8959b06..08a037f 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_browsertest.cc
@@ -235,8 +235,8 @@
   new TurnSyncOnHelper(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kUnknownReason, second_account_id, aborted_mode(),
-      std::move(owned_delegate), run_loop.QuitClosure());
+      second_account_id, aborted_mode(), std::move(owned_delegate),
+      run_loop.QuitClosure());
 
   delegate->WaitUntilBlock();
   EXPECT_EQ(Delegate::BlockingStep::kSyncConfirmation,
@@ -353,8 +353,7 @@
   base::WeakPtr<Delegate> delegate = owned_delegate->GetWeakPtr();
   new TurnSyncOnHelper(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
-      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kUnknownReason, account_id,
+      signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO, account_id,
       TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT,
       std::move(owned_delegate), run_loop.QuitClosure());
 
@@ -419,7 +418,7 @@
   new TurnSyncOnHelper(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kUnknownReason, first_account_id,
+      first_account_id,
       TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT_ON_WEB_ONLY,
       std::move(owned_delegate), run_loop.QuitClosure());
 
@@ -475,7 +474,7 @@
   new TurnSyncOnHelper(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kUnknownReason, second_account_id,
+      second_account_id,
       TurnSyncOnHelper::SigninAbortedMode::KEEP_ACCOUNT_ON_WEB_ONLY,
       std::move(owned_delegate), run_loop.QuitClosure());
 
@@ -534,8 +533,7 @@
   new TurnSyncOnHelper(
       profile, signin_metrics::AccessPoint::ACCESS_POINT_UNKNOWN,
       signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO,
-      signin_metrics::Reason::kUnknownReason, second_account_id,
-      TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT,
+      second_account_id, TurnSyncOnHelper::SigninAbortedMode::REMOVE_ACCOUNT,
       std::move(owned_delegate), run_loop.QuitClosure());
 
   delegate->WaitUntilBlock();
diff --git a/chrome/browser/ui/webui/signin/turn_sync_on_helper_unittest.cc b/chrome/browser/ui/webui/signin/turn_sync_on_helper_unittest.cc
index b819c8d..eb45f2b 100644
--- a/chrome/browser/ui/webui/signin/turn_sync_on_helper_unittest.cc
+++ b/chrome/browser/ui/webui/signin/turn_sync_on_helper_unittest.cc
@@ -96,8 +96,6 @@
     signin_metrics::AccessPoint::ACCESS_POINT_BOOKMARK_MANAGER;
 const signin_metrics::PromoAction kSigninPromoAction =
     signin_metrics::PromoAction::PROMO_ACTION_WITH_DEFAULT;
-const signin_metrics::Reason kSigninReason =
-    signin_metrics::Reason::kReauthentication;
 
 struct ExpectedMetricsState {
   // Access point that triggered sign-in, might be different from the one
@@ -564,8 +562,8 @@
     WeakClosure weak_closure;
 
     auto* helper = new TurnSyncOnHelper(
-        profile(), kAccessPoint, kSigninPromoAction, kSigninReason, account_id_,
-        mode, std::make_unique<TestTurnSyncOnHelperDelegate>(this),
+        profile(), kAccessPoint, kSigninPromoAction, account_id_, mode,
+        std::make_unique<TestTurnSyncOnHelperDelegate>(this),
         weak_closure.Get().Then(flow_completion_loop_.QuitClosure()));
 
     // In no circumstance should the flow complete synchronously. It can cause
@@ -670,8 +668,6 @@
     EXPECT_THAT(
         histogram_tester_->GetAllSamples("Signin.SigninCompletedAccessPoint"),
         BucketsAre(Bucket(kAccessPoint, expected.sign_in_recorded)));
-    EXPECT_THAT(histogram_tester_->GetAllSamples("Signin.SigninReason"),
-                BucketsAre(Bucket(kSigninReason, expected.sign_in_recorded)));
 
     EXPECT_THAT(histogram_tester_->GetAllSamples("Signin.SyncOptIn.Completed"),
                 BucketsAre(Bucket(kAccessPoint,
diff --git a/chrome/browser/web_applications/test/web_app_test.h b/chrome/browser/web_applications/test/web_app_test.h
index 0aeb552..3d988893 100644
--- a/chrome/browser/web_applications/test/web_app_test.h
+++ b/chrome/browser/web_applications/test/web_app_test.h
@@ -41,14 +41,13 @@
   explicit WebAppTest(WebAppTestTraits&&... traits)
       : content::RenderViewHostTestHarness(
             base::trait_helpers::Exclude<WithTestUrlLoaderFactory>::Filter(
-                traits)...),
-        shared_url_loader_factory_(
-            base::trait_helpers::HasTrait<WithTestUrlLoaderFactory,
-                                          WebAppTestTraits...>()
-                ? base::MakeRefCounted<
-                      network::WeakWrapperSharedURLLoaderFactory>(
-                      &test_url_loader_factory_)
-                : nullptr) {}
+                traits)...) {
+    shared_url_loader_factory_ =
+        base::trait_helpers::HasTrait<WithTestUrlLoaderFactory,
+                                      WebAppTestTraits...>()
+            ? test_url_loader_factory_.GetSafeWeakWrapper()
+            : nullptr;
+  }
 
   ~WebAppTest() override;
 
@@ -76,9 +75,9 @@
 
  private:
   base::TimeTicks start_time_ = base::TimeTicks::Now();
+  network::TestURLLoaderFactory test_url_loader_factory_;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_ =
       nullptr;
-  network::TestURLLoaderFactory test_url_loader_factory_;
 
   TestingProfileManager testing_profile_manager_{
       TestingBrowserProcess::GetGlobal()};
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
index 746976ad..d7b3d44 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/net/system_network_context_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_observer.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/device_info_sync_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -48,6 +49,7 @@
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync/base/features.h"
 #include "components/sync/protocol/webauthn_credential_specifics.pb.h"
 #include "components/user_prefs/user_prefs.h"
@@ -70,6 +72,7 @@
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/permissions/permissions_data.h"
+#include "google_apis/gaia/gaia_constants.h"
 #include "net/base/url_util.h"
 #include "third_party/icu/source/common/unicode/locid.h"
 #include "third_party/icu/source/i18n/unicode/timezone.h"
@@ -1232,6 +1235,23 @@
           Profile::FromBrowserContext(GetBrowserContext()));
   CHECK(passkey_model);
 
+  base::OnceCallback<void(GoogleServiceAuthError error,
+                          signin::AccessTokenInfo access_token_info)>
+      callback = base::BindOnce(
+          &ChromeAuthenticatorRequestDelegate::EnclaveAccessTokenFetched,
+          weak_ptr_factory_.GetWeakPtr(),
+          discovery_factory->get_enclave_oauth_token_callback());
+
+  auto* identity_manager = IdentityManagerFactory::GetForProfile(
+      Profile::FromBrowserContext(GetBrowserContext()));
+  access_token_fetcher_ =
+      std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
+          "enclave", identity_manager,
+          signin::ScopeSet{GaiaConstants::kPasskeysEnclaveOAuth2Scope},
+          std::move(callback),
+          signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate,
+          signin::ConsentLevel::kSignin);
+
   std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys =
       passkey_model->GetPasskeysForRelyingPartyId(rp_id);
   discovery_factory->set_enclave_passkeys(std::move(passkeys));
@@ -1240,6 +1260,23 @@
                           weak_ptr_factory_.GetWeakPtr()));
 }
 
+void ChromeAuthenticatorRequestDelegate::EnclaveAccessTokenFetched(
+    base::RepeatingCallback<void(absl::optional<std::string_view>)> callback,
+    GoogleServiceAuthError error,
+    signin::AccessTokenInfo access_token_info) {
+  access_token_fetcher_.release();
+
+  if (error.state() != GoogleServiceAuthError::NONE) {
+    FIDO_LOG(ERROR)
+        << "Cloud enclave authenticator access token retrieval failed "
+        << error.state();
+    callback.Run(absl::nullopt);
+    return;
+  }
+
+  callback.Run(access_token_info.token);
+}
+
 void ChromeAuthenticatorRequestDelegate::OnPasskeyCreated(
     sync_pb::WebauthnCredentialSpecifics passkey) {
   webauthn::PasskeyModel* passkey_model =
diff --git a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
index 2572fef0..1b69303 100644
--- a/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
+++ b/chrome/browser/webauthn/chrome_authenticator_request_delegate.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "base/functional/callback.h"
 #include "base/gtest_prod_util.h"
@@ -15,11 +16,14 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
+#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
 #include "content/public/browser/authenticator_request_client_delegate.h"
 #include "content/public/browser/global_routing_id.h"
 #include "device/fido/cable/cable_discovery_data.h"
 #include "device/fido/fido_request_handler_base.h"
 #include "device/fido/fido_transport_protocol.h"
+#include "google_apis/gaia/google_service_auth_error.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 class PrefService;
@@ -262,6 +266,12 @@
   // Invoked when a new GPM passkey is created, to save it to sync data.
   void OnPasskeyCreated(sync_pb::WebauthnCredentialSpecifics passkey);
 
+  // Invoked when an OAuth access token is available for the Enclave.
+  void EnclaveAccessTokenFetched(
+      base::RepeatingCallback<void(absl::optional<std::string_view>)> callback,
+      GoogleServiceAuthError error,
+      signin::AccessTokenInfo access_token_info);
+
 #if BUILDFLAG(IS_MAC)
   // DaysSinceDate returns the number of days between `formatted_date` (in ISO
   // 8601 format) and `now`. It returns `nullopt` if `formatted_date` cannot be
@@ -332,6 +342,10 @@
   // True when the cloud enclave authenticator is available for use.
   bool is_enclave_authenticator_available_ = false;
 
+  // Fetcher for OAuth access tokens needed to use the enclave authenticator.
+  std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
+      access_token_fetcher_;
+
   base::WeakPtrFactory<ChromeAuthenticatorRequestDelegate> weak_ptr_factory_{
       this};
 };
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index e2495ec..fcae0e6 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1703656685-4ceeedd1983a1474df6512d04b4bc2d25d37d7bc.profdata
+chrome-android32-main-1703699887-b7b5a1b6cf662080d4ff6160a7ff1fbf42ff0aec.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index f008ddb7..83c485dd 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1703656685-484810fd30a28e34694055a6a8d90b24dd817f5d.profdata
+chrome-android64-main-1703699887-adef7540ce01e38a008d3e5bb75bf085bcdba05b.profdata
diff --git a/chrome/build/lacros-arm64.pgo.txt b/chrome/build/lacros-arm64.pgo.txt
index 91c053f4..c1b2208 100644
--- a/chrome/build/lacros-arm64.pgo.txt
+++ b/chrome/build/lacros-arm64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-arm64-generic-main-1703635025-7a609de957b058cd3008335fa7971e14eef55433.profdata
+chrome-chromeos-arm64-generic-main-1703678262-c23ff425500e44dbc7ededc6e40f7ffb27eb854c.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 43b4844..c582f8c 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1703656685-6c3ada8f7270d5b8693b4a8790819a89999ea4e0.profdata
+chrome-linux-main-1703699887-fc0284ff1f4f0af5272ad7f572c6837c4b74d2e9.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index ef264eb..f6010d6 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1703656685-3f809da454b56ce78a267e2ed20f9aa1db0b25e5.profdata
+chrome-win-arm64-main-1703699887-d143abbc0907129f08d61457a3c0fea73b22c712.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 5fc0c8f4..03bc524 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1703656685-d48a250b9ac191874f5e79c08af37f38ec53c7d4.profdata
+chrome-win32-main-1703699887-a5553e522bff004627b52c2b21ccaffce785956c.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 4319ec2..ce3d4a2 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1703656685-bad04019e5a74364e58815b7b7b6d005e9aa431b.profdata
+chrome-win64-main-1703699887-b31c169884dd06741e6b5a30923fca564ed81ab3.profdata
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 89626fb8..5d496141 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -374,7 +374,9 @@
   dictionary ArcAppTracingInfo {
     boolean success;
     double fps;
+    double perceivedFps;
     double commitDeviation;
+    double presentDeviation;
     double renderQuality;
   };
 
diff --git a/chrome/common/extensions/chrome_manifest_url_handlers.cc b/chrome/common/extensions/chrome_manifest_url_handlers.cc
index 4623c1a5..fc6ab157 100644
--- a/chrome/common/extensions/chrome_manifest_url_handlers.cc
+++ b/chrome/common/extensions/chrome_manifest_url_handlers.cc
@@ -67,8 +67,7 @@
   GURL url = extension->GetResourceURL(*devtools_str);
   const bool is_extension_url =
       url.SchemeIs(kExtensionScheme) && url.host_piece() == extension->id();
-  // TODO(caseq): using http(s) is unsupported and will be disabled in m83.
-  if (!is_extension_url && !url.SchemeIsHTTPOrHTTPS()) {
+  if (!is_extension_url) {
     *error = errors::kInvalidDevToolsPage;
     return false;
   }
diff --git a/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc b/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
index feb8ea6..874a2d8 100644
--- a/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
+++ b/chrome/common/extensions/manifest_tests/extension_manifests_devtools_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/files/scoped_temp_dir.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/common/extensions/chrome_manifest_url_handlers.h"
 #include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
 #include "extensions/common/extension.h"
@@ -19,17 +21,41 @@
   LoadAndExpectError("devtools_extension_invalid_page_url.json",
                      extensions::manifest_errors::kInvalidDevToolsPage);
 
-  // TODO(caseq): the implementation should be changed to produce failure
-  // with the manifest below.
-  scoped_refptr<extensions::Extension> extension;
-  extension = LoadAndExpectSuccess("devtools_extension_page_url_https.json");
-  EXPECT_EQ("https://bad.example.com/dont_ever_do_this.html",
-            extensions::chrome_manifest_urls::GetDevToolsPage(extension.get())
-                .spec());
+  LoadAndExpectError("devtools_extension_page_url_https.json",
+                     extensions::manifest_errors::kInvalidDevToolsPage);
 
-  extension = LoadAndExpectSuccess("devtools_extension.json");
+  scoped_refptr<extensions::Extension> extension =
+      LoadAndExpectSuccess("devtools_extension.json");
   EXPECT_EQ(extension->url().spec() + "devtools.html",
             extensions::chrome_manifest_urls::GetDevToolsPage(extension.get())
                 .spec());
   EXPECT_TRUE(extension->permissions_data()->HasEffectiveAccessToAllHosts());
 }
+
+TEST_F(DevToolsPageManifestTest, DevToolsPageAbsoluteUrl) {
+  auto manifest = base::Value::Dict()
+                      .Set("name", "test")
+                      .Set("version", "1")
+                      .Set("manifest_version", 3)
+                      .Set("devtools_page", "chrome-extension://someid/path");
+  std::string error;
+  base::ScopedTempDir dir;
+  ASSERT_TRUE(dir.CreateUniqueTempDir());
+  scoped_refptr<extensions::Extension> extension =
+      extensions::Extension::Create(
+          dir.GetPath(), extensions::mojom::ManifestLocation::kInternal,
+          manifest, extensions::Extension::NO_FLAGS, "someid", &error);
+  ASSERT_TRUE(extension.get());
+  EXPECT_TRUE(error.empty());
+
+  // Specifying an absolute URL for a different extension's resource should
+  // fail, similar to other remote sources.
+  extension = extensions::Extension::Create(
+      dir.GetPath(), extensions::mojom::ManifestLocation::kInternal, manifest,
+      extensions::Extension::NO_FLAGS, "otherid", &error);
+  ASSERT_FALSE(extension);
+  ASSERT_FALSE(error.empty());
+  EXPECT_EQ(
+      base::UTF16ToUTF8(extensions::manifest_errors::kInvalidDevToolsPage),
+      error);
+}
diff --git a/chrome/renderer/accessibility/read_anything_app_model.cc b/chrome/renderer/accessibility/read_anything_app_model.cc
index e97b5d6..0815b7a 100644
--- a/chrome/renderer/accessibility/read_anything_app_model.cc
+++ b/chrome/renderer/accessibility/read_anything_app_model.cc
@@ -166,6 +166,13 @@
   ui::AXNode* end_node = GetAXNode(end_node_id_);
   DCHECK(end_node);
 
+  if (!start_node || !end_node) {
+    DUMP_WILL_BE_NOTREACHED_NORETURN()
+        << "Selection is invalid. Start node existed? " << !!start_node
+        << " End node existed? " << !!end_node;
+    return;
+  }
+
   // If start node or end node is invisible or ignored, the selection was
   // invalid.
   if (start_node->IsInvisibleOrIgnored() || end_node->IsInvisibleOrIgnored()) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 62d77ff..7ae1358b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -11604,6 +11604,8 @@
     "../browser/sync/test/integration/multi_client_status_change_checker.h",
     "../browser/sync/test/integration/offer_helper.cc",
     "../browser/sync/test/integration/offer_helper.h",
+    "../browser/sync/test/integration/password_sharing_invitation_helper.cc",
+    "../browser/sync/test/integration/password_sharing_invitation_helper.h",
     "../browser/sync/test/integration/passwords_helper.cc",
     "../browser/sync/test/integration/passwords_helper.h",
     "../browser/sync/test/integration/quiesce_status_change_checker.cc",
diff --git a/chrome/test/data/webui/compose/compose_app_test.ts b/chrome/test/data/webui/compose/compose_app_test.ts
index aae7710a..a284f22 100644
--- a/chrome/test/data/webui/compose/compose_app_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_test.ts
@@ -623,7 +623,7 @@
 
     assertTrue(isVisible(app.$.lengthMenu), 'Length menu should be visible.');
     assertEquals(
-        2, app.$.lengthMenu.querySelectorAll('option:not([hidden])').length);
+        2, app.$.lengthMenu.querySelectorAll('option:not([disabled])').length);
 
     app.$.lengthMenu.value = `${Length.kShorter}`;
     app.$.lengthMenu.dispatchEvent(new CustomEvent('change'));
@@ -637,7 +637,7 @@
 
     assertTrue(isVisible(app.$.toneMenu), 'Tone menu should be visible.');
     assertEquals(
-        2, app.$.toneMenu.querySelectorAll('option:not([hidden])').length);
+        2, app.$.toneMenu.querySelectorAll('option:not([disabled])').length);
 
     app.$.toneMenu.value = `${Tone.kCasual}`;
     app.$.toneMenu.dispatchEvent(new CustomEvent('change'));
diff --git a/chromeos/ash/components/audio/cras_audio_handler.cc b/chromeos/ash/components/audio/cras_audio_handler.cc
index 736f471..79cc902 100644
--- a/chromeos/ash/components/audio/cras_audio_handler.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler.cc
@@ -2406,11 +2406,6 @@
     // Unmute the audio output after the HDMI transition period.
     VLOG(1) << "Unmute output after HDMI rediscovering grace period.";
     SetOutputMuteInternal(false);
-
-    // Notify UI about the mute state change.
-    for (auto& observer : observers_) {
-      observer.OnOutputMuteChanged(output_mute_on_);
-    }
   }
 }
 
diff --git a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
index 4fe97590..9999cd0 100644
--- a/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
+++ b/chromeos/ash/components/audio/cras_audio_handler_unittest.cc
@@ -200,7 +200,7 @@
 
   int output_mute_changed_count() const { return output_mute_changed_count_; }
 
-  void reset_output_mute_changed_count() { input_mute_changed_count_ = 0; }
+  void reset_output_mute_changed_count() { output_mute_changed_count_ = 0; }
 
   int input_mute_changed_count() const { return input_mute_changed_count_; }
 
@@ -4603,8 +4603,8 @@
   EXPECT_FALSE(cras_audio_handler_->IsOutputMuted());
 }
 
-// This tests the case of output unmuting event is notified after the hdmi
-// output re-discover grace period ends, see crbug.com/512601.
+// This tests the case of output unmuting event is not notified after the hdmi
+// output re-discover grace period ends.
 TEST_P(CrasAudioHandlerTest, HDMIOutputUnplugDuringSuspension) {
   AudioNodeList audio_nodes =
       GenerateAudioNodeList({kInternalSpeaker, kHDMIOutput});
@@ -4638,7 +4638,7 @@
   EXPECT_TRUE(cras_audio_handler_->IsOutputMuted());
 
   // After HDMI re-discover grace period, verify internal speaker is still the
-  // active output and not muted, and unmute event by system is notified.
+  // active output and not muted.
   test_observer_->reset_output_mute_changed_count();
   HDMIRediscoverWaiter waiter(this, grace_period_in_ms);
   waiter.WaitUntilHDMIRediscoverGracePeriodEnd();
@@ -4648,7 +4648,7 @@
   EXPECT_EQ(kInternalSpeaker->id,
             cras_audio_handler_->GetPrimaryActiveOutputNode());
   EXPECT_FALSE(cras_audio_handler_->IsOutputMuted());
-  EXPECT_EQ(1, test_observer_->output_mute_changed_count());
+  EXPECT_EQ(0, test_observer_->output_mute_changed_count());
 }
 
 TEST_P(CrasAudioHandlerTest, FrontCameraStartStop) {
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager.cc b/chromeos/ash/components/fwupd/firmware_update_manager.cc
index ed31f6c..aad1cac 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager.cc
+++ b/chromeos/ash/components/fwupd/firmware_update_manager.cc
@@ -42,25 +42,6 @@
 namespace ash {
 
 namespace {
-// State of the fwupd daemon. Enum defined here:
-// https://github.com/fwupd/fwupd/blob/4389f9f913588edae7243a8dbed88ce3788c8bc2/libfwupd/fwupd-enums.h
-enum class FwupdStatus {
-  kUnknown,
-  kIdle,
-  kLoading,
-  kDecompressing,
-  kDeviceRestart,
-  kDeviceWrite,
-  kDeviceVerify,
-  kScheduling,
-  kDownloading,
-  kDeviceRead,
-  kDeviceErase,
-  kWaitingForAuth,
-  kDeviceBusy,
-  kShutdown,
-  kWaitingForUser,
-};
 
 static constexpr auto FwupdStatusStringMap =
     base::MakeFixedFlatMap<FwupdStatus, const char*>(
@@ -353,6 +334,7 @@
   install_controller_receiver_.reset();
   update_progress_observer_.reset();
   device_request_observer_.reset();
+  last_fwupd_status_ = FwupdStatus::kUnknown;
 }
 
 void FirmwareUpdateManager::PrepareForUpdate(
@@ -673,6 +655,9 @@
 void FirmwareUpdateManager::OnInstallResponse(bool success) {
   auto state = success ? firmware_update::mojom::UpdateState::kSuccess
                        : firmware_update::mojom::UpdateState::kFailed;
+  if (!success) {
+    firmware_update::metrics::EmitInstallFailedWithStatus(last_fwupd_status_);
+  }
   const auto result =
       success ? firmware_update::metrics::FirmwareUpdateInstallResult::kSuccess
               : firmware_update::metrics::FirmwareUpdateInstallResult::
@@ -726,6 +711,7 @@
     return;
   }
   const auto status = FwupdStatus(properties->status.value());
+  last_fwupd_status_ = status;
   const auto percentage = properties->percentage.value();
   VLOG(1) << "fwupd: OnPropertiesChangedResponse called with Status: "
           << GetFwupdStatusString(static_cast<FwupdStatus>(status))
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager.h b/chromeos/ash/components/fwupd/firmware_update_manager.h
index f1d8a7eb..90bb1961 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager.h
+++ b/chromeos/ash/components/fwupd/firmware_update_manager.h
@@ -35,6 +35,29 @@
 }  // namespace network
 
 namespace ash {
+
+// State of the fwupd daemon. Enum defined here:
+// https://github.com/fwupd/fwupd/blob/4389f9f913588edae7243a8dbed88ce3788c8bc2/libfwupd/fwupd-enums.h
+// Keep in sync with corresponding enum in tools/metrics/histograms/enums.xml.
+enum class FwupdStatus {
+  kUnknown,
+  kIdle,
+  kLoading,
+  kDecompressing,
+  kDeviceRestart,
+  kDeviceWrite,
+  kDeviceVerify,
+  kScheduling,
+  kDownloading,
+  kDeviceRead,
+  kDeviceErase,
+  kWaitingForAuth,
+  kDeviceBusy,
+  kShutdown,
+  kWaitingForUser,
+  kMaxValue = kWaitingForUser,
+};
+
 // FirmwareUpdateManager contains all logic that runs the firmware update SWA.
 class COMPONENT_EXPORT(CHROMEOS_ASH_COMPONENTS_FWUPD) FirmwareUpdateManager
     : public FwupdClient::Observer,
@@ -220,6 +243,9 @@
   // The device update that is currently inflight.
   firmware_update::mojom::FirmwareUpdatePtr inflight_update_;
 
+  // The most recent FwupdStatus, used for the purpose of recording metrics.
+  FwupdStatus last_fwupd_status_ = FwupdStatus::kUnknown;
+
   // Used to show the firmware update notification and to determine which
   // metric to fire (Startup/Refresh).
   bool is_first_response_ = true;
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc b/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
index 7fd7e28..c3eb608 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
+++ b/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
@@ -247,6 +247,30 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  firmware_update::mojom::FirmwareUpdatePtr CreateFakeUpdate() {
+    auto update = firmware_update::mojom::FirmwareUpdate::New();
+    update->device_id = "id";
+    update->device_name = base::UTF8ToUTF16(std::string("name"));
+    update->device_version = "version";
+    update->device_description = base::UTF8ToUTF16(std::string("description"));
+    update->priority = firmware_update::mojom::UpdatePriority::kMedium;
+    update->filepath = base::FilePath("filepath");
+    update->checksum = "checksum";
+    return update;
+  }
+
+  void TriggerInstallFailed() {
+    // Create a fake update so that the following method call works correctly.
+    firmware_update_manager_->inflight_update_ = CreateFakeUpdate();
+    // Trigger an unsuccessful update.
+    firmware_update_manager_->OnInstallResponse(/*success=*/false);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void SetStatus(FwupdStatus fwupd_status) {
+    SetProperties(/*percentage=*/0, static_cast<uint32_t>(fwupd_status));
+  }
+
   void SetFakeUrlForTesting(const std::string& fake_url) {
     firmware_update_manager_->SetFakeUrlForTesting(fake_url);
   }
@@ -1158,4 +1182,53 @@
       firmware_update::mojom::DeviceRequestId::kPressUnlock, 1);
 }
 
+struct FailedInstallParam {
+  explicit FailedInstallParam(FwupdStatus fwupd_status)
+      : fwupd_status(fwupd_status) {}
+
+  FwupdStatus fwupd_status;
+};
+
+class FirmwareUpdateManagerTest_FailedInstall
+    : public FirmwareUpdateManagerTest,
+      public testing::WithParamInterface<FailedInstallParam> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    FirmwareUpdateManagerTest_FailedInstall,
+    FirmwareUpdateManagerTest_FailedInstall,
+    ::testing::Values(FailedInstallParam(FwupdStatus::kUnknown),
+                      FailedInstallParam(FwupdStatus::kIdle),
+                      FailedInstallParam(FwupdStatus::kLoading),
+                      FailedInstallParam(FwupdStatus::kDecompressing),
+                      FailedInstallParam(FwupdStatus::kDeviceRestart),
+                      FailedInstallParam(FwupdStatus::kDeviceWrite),
+                      FailedInstallParam(FwupdStatus::kDeviceVerify),
+                      FailedInstallParam(FwupdStatus::kScheduling),
+                      FailedInstallParam(FwupdStatus::kDownloading),
+                      FailedInstallParam(FwupdStatus::kDeviceRead),
+                      FailedInstallParam(FwupdStatus::kDeviceErase),
+                      FailedInstallParam(FwupdStatus::kWaitingForAuth),
+                      FailedInstallParam(FwupdStatus::kDeviceBusy),
+                      FailedInstallParam(FwupdStatus::kShutdown),
+                      FailedInstallParam(FwupdStatus::kWaitingForUser)));
+
+TEST_P(FirmwareUpdateManagerTest_FailedInstall, FailedInstall_WaitingForUser) {
+  base::HistogramTester histogram_tester;
+
+  // These three steps are necessary for SetStatus and TriggerInstallFailed to
+  // work correctly.
+  EXPECT_TRUE(PrepareForUpdate(std::string(kFakeDeviceIdForTesting)));
+  FakeUpdateProgressObserver update_progress_observer;
+  SetupProgressObserver(&update_progress_observer);
+
+  SetStatus(GetParam().fwupd_status);
+  TriggerInstallFailed();
+
+  histogram_tester.ExpectTotalCount(
+      "ChromeOS.FirmwareUpdateUi.InstallFailedWithStatus", 1);
+  histogram_tester.ExpectUniqueSample(
+      "ChromeOS.FirmwareUpdateUi.InstallFailedWithStatus",
+      GetParam().fwupd_status, 1);
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/components/fwupd/histogram_util.cc b/chromeos/ash/components/fwupd/histogram_util.cc
index ba506c8f..0512498 100644
--- a/chromeos/ash/components/fwupd/histogram_util.cc
+++ b/chromeos/ash/components/fwupd/histogram_util.cc
@@ -32,6 +32,11 @@
                               num_updates);
 }
 
+void EmitInstallFailedWithStatus(FwupdStatus last_fwupd_status) {
+  base::UmaHistogramEnumeration(
+      "ChromeOS.FirmwareUpdateUi.InstallFailedWithStatus", last_fwupd_status);
+}
+
 void EmitInstallResult(FirmwareUpdateInstallResult result) {
   base::UmaHistogramEnumeration("ChromeOS.FirmwareUpdateUi.InstallResult",
                                 result);
diff --git a/chromeos/ash/components/fwupd/histogram_util.h b/chromeos/ash/components/fwupd/histogram_util.h
index 5275350..0f00fa87 100644
--- a/chromeos/ash/components/fwupd/histogram_util.h
+++ b/chromeos/ash/components/fwupd/histogram_util.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "ash/webui/firmware_update_ui/mojom/firmware_update.mojom.h"
+#include "chromeos/ash/components/fwupd/firmware_update_manager.h"
 
 namespace ash::firmware_update::metrics {
 
@@ -31,6 +32,7 @@
                      int num_critical_updates,
                      bool is_startup);
 
+void EmitInstallFailedWithStatus(FwupdStatus last_fwupd_status);
 void EmitInstallResult(FirmwareUpdateInstallResult result);
 void EmitDeviceRequest(firmware_update::mojom::DeviceRequestPtr request);
 
diff --git a/chromeos/profiles/arm-exp.afdo.newest.txt b/chromeos/profiles/arm-exp.afdo.newest.txt
index 75bc69b..c118873 100644
--- a/chromeos/profiles/arm-exp.afdo.newest.txt
+++ b/chromeos/profiles/arm-exp.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-exp-121-6167.9-1702295304-benchmark-122.0.6191.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-exp-122-6167.14-1703504610-benchmark-122.0.6195.0-r3-redacted.afdo.xz
diff --git a/chromeos/profiles/arm.afdo.newest.txt b/chromeos/profiles/arm.afdo.newest.txt
index 9b794e02..c522991 100644
--- a/chromeos/profiles/arm.afdo.newest.txt
+++ b/chromeos/profiles/arm.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-none-121-6154.0-1702299075-benchmark-122.0.6191.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-none-122-6167.14-1703508819-benchmark-122.0.6195.0-r3-redacted.afdo.xz
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index 08dc9c97..e87b76cf 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-122-6167.14-1702898748-benchmark-122.0.6192.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-122-6167.14-1703504610-benchmark-122.0.6195.0-r3-redacted.afdo.xz
diff --git a/clank b/clank
index 99685ad..8224cc0 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 99685ad0653a10fa9254bf04244740661468e99e
+Subproject commit 8224cc0dada49684ca5f40d26820ec2d3962ee33
diff --git a/components/certificate_transparency/data/log_list.json b/components/certificate_transparency/data/log_list.json
index 3e21953..4233d7b3 100644
--- a/components/certificate_transparency/data/log_list.json
+++ b/components/certificate_transparency/data/log_list.json
@@ -1,6 +1,6 @@
 {
-  "version": "29.7",
-  "log_list_timestamp": "2023-12-26T12:55:30Z",
+  "version": "29.8",
+  "log_list_timestamp": "2023-12-27T12:54:53Z",
   "operators": [
     {
       "name": "Google",
diff --git a/components/feedback/content/content_tracing_manager.cc b/components/feedback/content/content_tracing_manager.cc
index efddca3..dd270ba 100644
--- a/components/feedback/content/content_tracing_manager.cc
+++ b/components/feedback/content/content_tracing_manager.cc
@@ -103,6 +103,10 @@
   }
 }
 
+base::WeakPtr<TracingManager> ContentTracingManager::AsWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void ContentTracingManager::StartTracing() {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   content::TracingController::GetInstance()->StartTracing(
diff --git a/components/feedback/content/content_tracing_manager.h b/components/feedback/content/content_tracing_manager.h
index c06027f..ef00a480 100644
--- a/components/feedback/content/content_tracing_manager.h
+++ b/components/feedback/content/content_tracing_manager.h
@@ -25,7 +25,7 @@
 // version of the performance data.  That data can then be requested via
 // GetTraceData().  When the data is no longer needed, it should be discarded
 // via DiscardTraceData().
-class ContentTracingManager : public TracingManager {
+class ContentTracingManager final : public TracingManager {
  public:
   ~ContentTracingManager() override;
 
@@ -46,6 +46,8 @@
   // Discard the data for trace |id|.
   void DiscardTraceData(int id) override;
 
+  base::WeakPtr<TracingManager> AsWeakPtr() override;
+
  private:
   ContentTracingManager();
 
diff --git a/components/feedback/feedback_data.cc b/components/feedback/feedback_data.cc
index ce42b09..26d3331 100644
--- a/components/feedback/feedback_data.cc
+++ b/components/feedback/feedback_data.cc
@@ -41,7 +41,7 @@
   // sending the report. If it is created after this point, then the tracing is
   // not relevant to this report.
   if (tracing_manager) {
-    tracing_manager_ = base::AsWeakPtr(tracing_manager);
+    tracing_manager_ = tracing_manager->AsWeakPtr();
   }
 }
 
diff --git a/components/feedback/tracing_manager.h b/components/feedback/tracing_manager.h
index 9da2b23f..d638223 100644
--- a/components/feedback/tracing_manager.h
+++ b/components/feedback/tracing_manager.h
@@ -25,7 +25,7 @@
 // of the performance data.  That data can then be requested via GetTraceData().
 // When the data is no longer needed, it should be discarded via
 // DiscardTraceData().
-class TracingManager : public base::SupportsWeakPtr<TracingManager> {
+class TracingManager {
  public:
   virtual ~TracingManager();
 
@@ -43,9 +43,12 @@
   // Discard the data for trace |id|.
   virtual void DiscardTraceData(int id) = 0;
 
+  // Derived classes must implement this and return pointers from
+  // a `base::WeakPtrFactory` data member.
+  virtual base::WeakPtr<TracingManager> AsWeakPtr() = 0;
+
  protected:
   TracingManager();
 };
 
 #endif  // COMPONENTS_FEEDBACK_TRACING_MANAGER_H_
-
diff --git a/components/history/core/browser/expire_history_backend.h b/components/history/core/browser/expire_history_backend.h
index 6dd21fc9..b019bff 100644
--- a/components/history/core/browser/expire_history_backend.h
+++ b/components/history/core/browser/expire_history_backend.h
@@ -281,9 +281,8 @@
   raw_ptr<HistoryBackendNotifier> notifier_;
 
   // Non-owning pointers to the databases we deal with (MAY BE NULL).
-  raw_ptr<HistoryDatabase, AcrossTasksDanglingUntriaged>
-      main_db_;  // Main history database.
-  raw_ptr<favicon::FaviconDatabase, AcrossTasksDanglingUntriaged> favicon_db_;
+  raw_ptr<HistoryDatabase> main_db_;  // Main history database.
+  raw_ptr<favicon::FaviconDatabase> favicon_db_;
 
   // The threshold for "old" history where we will automatically delete it.
   base::TimeDelta expiration_threshold_;
@@ -310,7 +309,7 @@
   std::unique_ptr<ExpiringVisitsReader> auto_subframe_visits_reader_;
 
   // The HistoryBackendClient; may be null.
-  raw_ptr<HistoryBackendClient, AcrossTasksDanglingUntriaged> backend_client_;
+  raw_ptr<HistoryBackendClient> backend_client_;
 
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
diff --git a/components/history/core/browser/history_backend.cc b/components/history/core/browser/history_backend.cc
index de94ef1..7c6deea 100644
--- a/components/history/core/browser/history_backend.cc
+++ b/components/history/core/browser/history_backend.cc
@@ -363,9 +363,9 @@
     std::unique_ptr<HistoryBackendClient> backend_client,
     scoped_refptr<base::SequencedTaskRunner> task_runner)
     : delegate_(std::move(delegate)),
-      expirer_(this, backend_client.get(), task_runner),
       recent_redirects_(kMaxRedirectCount),
       backend_client_(std::move(backend_client)),
+      expirer_(this, backend_client_.get(), task_runner),
       task_runner_(task_runner) {
   DCHECK(delegate_);
 }
@@ -1334,6 +1334,9 @@
 }
 
 void HistoryBackend::CloseAllDatabases() {
+  // Reset to avoid dangling pointers to the database.
+  history_sync_bridge_.reset();
+  expirer_.SetDatabases(/*main_db=*/nullptr, /*favicon_db=*/nullptr);
   if (db_) {
     CommitSingletonTransactionIfItExists();
     db_.reset();
@@ -3246,7 +3249,6 @@
   singleton_transaction_ = db_->CreateTransaction();
 
   bool success = singleton_transaction_->Begin();
-  UMA_HISTOGRAM_BOOLEAN("History.Backend.TransactionBeginSuccess", success);
   if (success) {
     DCHECK_EQ(db_->transaction_nesting(), 1);
   } else {
diff --git a/components/history/core/browser/history_backend.h b/components/history/core/browser/history_backend.h
index 1e316477..f8ae30f5 100644
--- a/components/history/core/browser/history_backend.h
+++ b/components/history/core/browser/history_backend.h
@@ -1042,9 +1042,6 @@
   bool scheduled_kill_db_ = false;  // Database is being killed due to error.
   std::unique_ptr<favicon::FaviconBackend> favicon_backend_;
 
-  // Manages expiration between the various databases.
-  ExpireHistoryBackend expirer_;
-
   // A commit has been scheduled to occur sometime in the future. We can check
   // !IsCancelled() to see if there is a commit scheduled in the future (note
   // that CancelableOnceClosure starts cancelled with the default constructor),
@@ -1080,6 +1077,9 @@
   // Used to determine if a URL is bookmarked; may be null.
   std::unique_ptr<HistoryBackendClient> backend_client_;
 
+  // Manages expiration between the various databases.
+  ExpireHistoryBackend expirer_;
+
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
   // Listens for the system being under memory pressure.
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 50ec1cc..bcc3f857 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -217,6 +217,7 @@
   using HistoryBackend::UpdateVisitDuration;
 
   using HistoryBackend::db_;
+  using HistoryBackend::expirer_;
   using HistoryBackend::favicon_backend_;
   using HistoryBackend::recent_redirects_;
 
@@ -2851,6 +2852,7 @@
 // Check that UpdateFaviconMappingsAndFetch() call back to the UI when there is
 // no valid favicon database.
 TEST_F(HistoryBackendTest, UpdateFaviconMappingsAndFetchNoDB) {
+  backend_->expirer_.SetDatabases(/*main_db=*/nullptr, /*favicon_db=*/nullptr);
   // Make the favicon database invalid.
   backend_->favicon_backend_.reset();
 
diff --git a/components/history/core/browser/sync/history_sync_bridge.h b/components/history/core/browser/sync/history_sync_bridge.h
index af9875b..f166f90a 100644
--- a/components/history/core/browser/sync/history_sync_bridge.h
+++ b/components/history/core/browser/sync/history_sync_bridge.h
@@ -154,8 +154,7 @@
 
   // A non-owning pointer to the database, which is for storing sync metadata
   // and state. Can be null in case of unrecoverable database errors.
-  raw_ptr<HistorySyncMetadataDatabase, AcrossTasksDanglingUntriaged>
-      sync_metadata_database_;
+  raw_ptr<HistorySyncMetadataDatabase> sync_metadata_database_;
 
   // HistoryBackend uses SequencedTaskRunner, so this makes sure
   // HistorySyncBridge is used on the correct sequence.
diff --git a/components/omnibox/browser/in_memory_url_index.h b/components/omnibox/browser/in_memory_url_index.h
index 3c963151..25cb364 100644
--- a/components/omnibox/browser/in_memory_url_index.h
+++ b/components/omnibox/browser/in_memory_url_index.h
@@ -16,7 +16,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
-#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/threading/thread_checker.h"
@@ -72,7 +71,6 @@
 // multi-char16 instance.
 class InMemoryURLIndex : public KeyedService,
                          public history::HistoryServiceObserver,
-                         public base::SupportsWeakPtr<InMemoryURLIndex>,
                          public base::trace_event::MemoryDumpProvider {
  public:
   // `history_service` may be null during unit testing.
diff --git a/components/omnibox/browser/omnibox_client.h b/components/omnibox/browser/omnibox_client.h
index 972f4591..59589e2b 100644
--- a/components/omnibox/browser/omnibox_client.h
+++ b/components/omnibox/browser/omnibox_client.h
@@ -51,7 +51,7 @@
 // (e.g., getting information about the current page, retrieving objects
 // associated with the current tab, or performing operations that rely on such
 // objects under the hood).
-class OmniboxClient : public base::SupportsWeakPtr<OmniboxClient> {
+class OmniboxClient {
  public:
   OmniboxClient() = default;
   virtual ~OmniboxClient() = default;
@@ -219,6 +219,8 @@
   virtual void OnPopupVisibilityChanged() {}
 
   virtual LocationBarModel* GetLocationBarModel() = 0;
+
+  virtual base::WeakPtr<OmniboxClient> AsWeakPtr() = 0;
 };
 
 #endif  // COMPONENTS_OMNIBOX_BROWSER_OMNIBOX_CLIENT_H_
diff --git a/components/omnibox/browser/test_omnibox_client.cc b/components/omnibox/browser/test_omnibox_client.cc
index 24e8515..2963bb6 100644
--- a/components/omnibox/browser/test_omnibox_client.cc
+++ b/components/omnibox/browser/test_omnibox_client.cc
@@ -111,3 +111,7 @@
 void TestOmniboxClient::OnURLOpenedFromOmnibox(OmniboxLog* log) {
   last_log_disposition_ = log->disposition;
 }
+
+base::WeakPtr<OmniboxClient> TestOmniboxClient::AsWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
diff --git a/components/omnibox/browser/test_omnibox_client.h b/components/omnibox/browser/test_omnibox_client.h
index 20f70a6..07b1f06d 100644
--- a/components/omnibox/browser/test_omnibox_client.h
+++ b/components/omnibox/browser/test_omnibox_client.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "components/omnibox/browser/autocomplete_classifier.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
@@ -22,7 +23,7 @@
 class AutocompleteSchemeClassifier;
 
 // Fake implementation of OmniboxClient for use in tests.
-class TestOmniboxClient : public testing::NiceMock<OmniboxClient> {
+class TestOmniboxClient final : public testing::NiceMock<OmniboxClient> {
  public:
   TestOmniboxClient();
   ~TestOmniboxClient() override;
@@ -67,6 +68,8 @@
   MOCK_METHOD(bookmarks::BookmarkModel*, GetBookmarkModel, ());
   MOCK_METHOD(PrefService*, GetPrefs, ());
 
+  base::WeakPtr<OmniboxClient> AsWeakPtr() override;
+
   WindowOpenDisposition last_log_disposition() const {
     return last_log_disposition_;
   }
@@ -77,6 +80,7 @@
   TestSchemeClassifier scheme_classifier_;
   AutocompleteClassifier autocomplete_classifier_;
   WindowOpenDisposition last_log_disposition_;
+  base::WeakPtrFactory<TestOmniboxClient> weak_factory_{this};
 };
 
 #endif  // COMPONENTS_OMNIBOX_BROWSER_TEST_OMNIBOX_CLIENT_H_
diff --git a/components/optimization_guide/core/hints_manager.h b/components/optimization_guide/core/hints_manager.h
index 939f2f7..de5d258 100644
--- a/components/optimization_guide/core/hints_manager.h
+++ b/components/optimization_guide/core/hints_manager.h
@@ -522,8 +522,7 @@
   raw_ptr<TopHostProvider, DanglingUntriaged> top_host_provider_ = nullptr;
 
   // The tab URL provider that can be queried. Not owned.
-  raw_ptr<TabUrlProvider, AcrossTasksDanglingUntriaged> tab_url_provider_ =
-      nullptr;
+  raw_ptr<TabUrlProvider> tab_url_provider_ = nullptr;
 
   // The timer used to schedule fetching hints from the remote Optimization
   // Guide Service.
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index 1bd7f39..5e31ebc 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit 1bd7f39830947c4615bf1d40810c3adf862d20bc
+Subproject commit 5e31ebc3259a7849928fafb8bdeb3cd8ca48c8d8
diff --git a/components/page_load_metrics/browser/metrics_web_contents_observer.h b/components/page_load_metrics/browser/metrics_web_contents_observer.h
index 40adc4484..a07f3d23 100644
--- a/components/page_load_metrics/browser/metrics_web_contents_observer.h
+++ b/components/page_load_metrics/browser/metrics_web_contents_observer.h
@@ -12,6 +12,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/read_only_shared_memory_region.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "components/page_load_metrics/browser/page_load_metrics_observer.h"
@@ -47,7 +48,6 @@
     : public content::WebContentsObserver,
       public content::WebContentsUserData<MetricsWebContentsObserver>,
       public content::RenderWidgetHost::InputEventObserver,
-      public base::SupportsWeakPtr<MetricsWebContentsObserver>,
       public mojom::PageLoadMetrics {
  public:
   // Record a set of WebFeatures directly from the browser process. This
@@ -170,6 +170,10 @@
   // Returns the time this MetricsWebContentsObserver was created.
   base::TimeTicks GetCreated();
 
+  base::WeakPtr<MetricsWebContentsObserver> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  protected:
   // Protected rather than private so that derived test classes can call
   // constructor.
@@ -363,6 +367,8 @@
 
   base::TimeTicks created_;
 
+  base::WeakPtrFactory<MetricsWebContentsObserver> weak_ptr_factory_{this};
+
   WEB_CONTENTS_USER_DATA_KEY_DECL();
 };
 
diff --git a/components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h b/components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h
index 5b59920c..e1829b0 100644
--- a/components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h
+++ b/components/page_load_metrics/browser/observers/ad_metrics/frame_tree_data.h
@@ -121,7 +121,7 @@
 // typically used to capture an ad creative. It stores frame-specific
 // information (such as size, activation status, and origin), which is typically
 // specific to the top frame in the tree.
-class FrameTreeData : public base::SupportsWeakPtr<FrameTreeData> {
+class FrameTreeData final {
  public:
   using FrameTreeNodeId = PageLoadMetricsObserver::FrameTreeNodeId;
 
@@ -255,6 +255,10 @@
     return peak_cpu_.peak_windowed_percent();
   }
 
+  base::WeakPtr<FrameTreeData> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
  private:
   // Updates whether or not this frame meets the criteria for visibility.
   void UpdateFrameVisibility();
@@ -345,6 +349,9 @@
 
   // Memory usage by v8 in this ad frame tree.
   MemoryUsageAggregator memory_usage_;
+
+  // Owns weak pointers to the instance.
+  base::WeakPtrFactory<FrameTreeData> weak_ptr_factory_{this};
 };
 
 }  // namespace page_load_metrics
diff --git a/components/page_load_metrics/common/test/weak_mock_timer.cc b/components/page_load_metrics/common/test/weak_mock_timer.cc
index a7a1ce1..ae10ec03 100644
--- a/components/page_load_metrics/common/test/weak_mock_timer.cc
+++ b/components/page_load_metrics/common/test/weak_mock_timer.cc
@@ -7,10 +7,11 @@
 namespace page_load_metrics {
 namespace test {
 
-WeakMockTimer::WeakMockTimer() {}
+WeakMockTimer::WeakMockTimer() = default;
+WeakMockTimer::~WeakMockTimer() = default;
 
-WeakMockTimerProvider::WeakMockTimerProvider() {}
-WeakMockTimerProvider::~WeakMockTimerProvider() {}
+WeakMockTimerProvider::WeakMockTimerProvider() = default;
+WeakMockTimerProvider::~WeakMockTimerProvider() = default;
 
 base::MockOneShotTimer* WeakMockTimerProvider::GetMockTimer() const {
   return timer_.get();
diff --git a/components/page_load_metrics/common/test/weak_mock_timer.h b/components/page_load_metrics/common/test/weak_mock_timer.h
index c82982e..4ff4274 100644
--- a/components/page_load_metrics/common/test/weak_mock_timer.h
+++ b/components/page_load_metrics/common/test/weak_mock_timer.h
@@ -12,13 +12,20 @@
 namespace test {
 
 // WeakMockTimer is a MockTimer that allows clients to keep WeakPtr<>s to it.
-class WeakMockTimer : public base::MockOneShotTimer,
-                      public base::SupportsWeakPtr<WeakMockTimer> {
+class WeakMockTimer final : public base::MockOneShotTimer {
  public:
   WeakMockTimer();
+  ~WeakMockTimer() override;
 
   WeakMockTimer(const WeakMockTimer&) = delete;
   WeakMockTimer& operator=(const WeakMockTimer&) = delete;
+
+  base::WeakPtr<WeakMockTimer> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<WeakMockTimer> weak_ptr_factory_{this};
 };
 
 // WeakMockTimerProvider is a testing helper class that test classes can inherit
diff --git a/components/password_manager/core/browser/affiliation/affiliation_service_impl.cc b/components/password_manager/core/browser/affiliation/affiliation_service_impl.cc
index 7c531089..7d6a6938 100644
--- a/components/password_manager/core/browser/affiliation/affiliation_service_impl.cc
+++ b/components/password_manager/core/browser/affiliation/affiliation_service_impl.cc
@@ -248,7 +248,10 @@
 void AffiliationServiceImpl::GetGroupingInfo(std::vector<FacetURI> facet_uris,
                                              GroupsCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(backend_);
+  // If `backend` is destroyed there is nothing to do.
+  if (!backend_) {
+    return;
+  }
 
   backend_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
@@ -260,7 +263,11 @@
 void AffiliationServiceImpl::GetPSLExtensions(
     base::OnceCallback<void(std::vector<std::string>)> callback) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(backend_);
+  // If `backend` is destroyed there is nothing to do.
+  if (!backend_) {
+    return;
+  }
+
   backend_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE,
       base::BindOnce(&AffiliationBackend::GetPSLExtensions,
diff --git a/components/password_manager/core/browser/affiliation/affiliation_service_impl.h b/components/password_manager/core/browser/affiliation/affiliation_service_impl.h
index d88c890f..6afb118 100644
--- a/components/password_manager/core/browser/affiliation/affiliation_service_impl.h
+++ b/components/password_manager/core/browser/affiliation/affiliation_service_impl.h
@@ -136,7 +136,11 @@
   template <typename Method, typename... Args>
   void PostToBackend(const Method& method, Args&&... args) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    CHECK(backend_);
+    // If `backend` is destroyed there is nothing to do.
+    if (!backend_) {
+      return;
+    }
+
     backend_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(method, base::Unretained(backend_.get()),
                                   std::forward<Args>(args)...));
diff --git a/components/password_manager/core/browser/password_reuse_detector.cc b/components/password_manager/core/browser/password_reuse_detector.cc
index 84c5807..9b8a357 100644
--- a/components/password_manager/core/browser/password_reuse_detector.cc
+++ b/components/password_manager/core/browser/password_reuse_detector.cc
@@ -24,14 +24,6 @@
 
 namespace password_manager {
 
-size_t GetMinPasswordLengthToCheck() {
-  if (base::FeatureList::IsEnabled(
-          safe_browsing::kEvaluateProtectedPasswordLengthMinimum)) {
-    return safe_browsing::kEvaluateProtectedPasswordLengthMinimumValue.Get();
-  }
-  return kMinPasswordLengthToCheck;
-}
-
 namespace {
 // Returns true iff |suffix_candidate| is a suffix of |str|.
 bool IsSuffix(const std::u16string& str,
@@ -131,7 +123,7 @@
     PasswordReuseDetectorConsumer* consumer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(consumer);
-  if (input.size() < GetMinPasswordLengthToCheck()) {
+  if (input.size() < kMinPasswordLengthToCheck) {
     consumer->OnReuseCheckDone(false, 0, std::nullopt, {},
                                SavedPasswordsCount(), std::string(), 0);
     return;
@@ -338,7 +330,7 @@
 
 void PasswordReuseDetector::AddPassword(const PasswordForm& form) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (form.password_value.size() < GetMinPasswordLengthToCheck()) {
+  if (form.password_value.size() < kMinPasswordLengthToCheck) {
     return;
   }
 
@@ -348,7 +340,7 @@
 
 void PasswordReuseDetector::RemovePassword(const PasswordForm& form) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (form.password_value.size() < GetMinPasswordLengthToCheck()) {
+  if (form.password_value.size() < kMinPasswordLengthToCheck) {
     return;
   }
 
diff --git a/components/password_manager/core/browser/password_reuse_detector.h b/components/password_manager/core/browser/password_reuse_detector.h
index 5d3d6b8..df2876f 100644
--- a/components/password_manager/core/browser/password_reuse_detector.h
+++ b/components/password_manager/core/browser/password_reuse_detector.h
@@ -24,12 +24,7 @@
 // Minimum number of characters in a password for finding it as password reuse.
 // It does not make sense to consider short strings for password reuse, since it
 // is quite likely that they are parts of common words.
-inline constexpr size_t kMinPasswordLengthToCheck = 8;
-
-// When kEvaluateProtectedPasswordLengthMinimum is enabled, return our
-// experimental minimum password length value. When it is not enabled, return
-// kMinPasswordLengthToCheck.
-size_t GetMinPasswordLengthToCheck();
+inline constexpr size_t kMinPasswordLengthToCheck = 4;
 
 class PasswordReuseDetectorConsumer;
 
diff --git a/components/password_manager/core/browser/store_metrics_reporter.cc b/components/password_manager/core/browser/store_metrics_reporter.cc
index b5d8b255..1f55520 100644
--- a/components/password_manager/core/browser/store_metrics_reporter.cc
+++ b/components/password_manager/core/browser/store_metrics_reporter.cc
@@ -449,9 +449,8 @@
     const std::vector<std::unique_ptr<PasswordForm>>& forms) {
   for (const std::unique_ptr<PasswordForm>& form : forms) {
     if (!form->blocked_by_user && form->password_value.size() > 0) {
-      metrics_util::LogIsPasswordProtected(
-          form->password_value.size() >=
-          password_manager::GetMinPasswordLengthToCheck());
+      metrics_util::LogIsPasswordProtected(form->password_value.size() >=
+                                           kMinPasswordLengthToCheck);
     }
   }
 }
diff --git a/components/safe_browsing/core/common/features.cc b/components/safe_browsing/core/common/features.cc
index c459ce6f..734be8a0 100644
--- a/components/safe_browsing/core/common/features.cc
+++ b/components/safe_browsing/core/common/features.cc
@@ -76,14 +76,6 @@
              "SafeBrowsingEncryptedArchivesMetadata",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kEvaluateProtectedPasswordLengthMinimum,
-             "EvaluateProtectedPasswordLengthMinimum",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-const base::FeatureParam<int> kEvaluateProtectedPasswordLengthMinimumValue{
-    &kEvaluateProtectedPasswordLengthMinimum, "MinimumValue",
-    /*default_value=*/4};
-
 BASE_FEATURE(kExtensionTelemetryConfiguration,
              "SafeBrowsingExtensionTelemetryConfiguration",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -330,7 +322,6 @@
     {&kCreateWarningShownClientSafeBrowsingReports, false},
     {&kDelayedWarnings, true},
     {&kDownloadTailoredWarnings, true},
-    {&kEvaluateProtectedPasswordLengthMinimum, false},
     {&kExtensionTelemetryDisableOffstoreExtensions, true},
     {&kExtensionTelemetryInterceptRemoteHostsContactedInRenderer, true},
     {&kExtensionTelemetryPotentialPasswordTheft, true},
diff --git a/components/safe_browsing/core/common/features.h b/components/safe_browsing/core/common/features.h
index b149a6be..d32515c5 100644
--- a/components/safe_browsing/core/common/features.h
+++ b/components/safe_browsing/core/common/features.h
@@ -68,13 +68,6 @@
 // passwords for local decryption on encrypted archives.
 BASE_DECLARE_FEATURE(kEncryptedArchivesMetadata);
 
-// Enables decreased Phishguard password length minimum.
-BASE_DECLARE_FEATURE(kEvaluateProtectedPasswordLengthMinimum);
-
-// Specifies the minimum password length for password protection.
-extern const base::FeatureParam<int>
-    kEvaluateProtectedPasswordLengthMinimumValue;
-
 // Allows the Extension Telemetry Service to accept and use configurations
 // sent by the server.
 BASE_DECLARE_FEATURE(kExtensionTelemetryConfiguration);
diff --git a/components/signin/public/base/signin_metrics.cc b/components/signin/public/base/signin_metrics.cc
index 2906032..3500222 100644
--- a/components/signin/public/base/signin_metrics.cc
+++ b/components/signin/public/base/signin_metrics.cc
@@ -124,10 +124,6 @@
   }
 }
 
-void LogSigninReason(Reason reason) {
-  base::UmaHistogramEnumeration("Signin.SigninReason", reason);
-}
-
 void LogSignInOffered(AccessPoint access_point) {
   base::UmaHistogramEnumeration("Signin.SignIn.Offered", access_point,
                                 AccessPoint::ACCESS_POINT_MAX);
diff --git a/components/signin/public/base/signin_metrics.h b/components/signin/public/base/signin_metrics.h
index 7a6e936..ffa89b9 100644
--- a/components/signin/public/base/signin_metrics.h
+++ b/components/signin/public/base/signin_metrics.h
@@ -318,9 +318,6 @@
 // numeric values should never be reused.
 // Please keep in Sync with "SigninReason" in
 // src/tools/metrics/histograms/enums.xml.
-// A Java counterpart will be generated for this enum.
-// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.components.signin.metrics
-// GENERATED_JAVA_CLASS_NAME_OVERRIDE: SigninReason
 enum class Reason : int {
   kSigninPrimaryAccount = 0,
   kAddSecondaryAccount = 1,
@@ -482,9 +479,6 @@
 void LogSigninAccessPointCompleted(AccessPoint access_point,
                                    PromoAction promo_action);
 
-// Tracks the reason of sign in.
-void LogSigninReason(Reason reason);
-
 // Logs sign in offered events and their associated access points.
 // Access points (or features) are responsible for recording this where relevant
 // for them.
diff --git a/components/supervised_user/core/browser/BUILD.gn b/components/supervised_user/core/browser/BUILD.gn
index 267c664c..9ffaaaf5 100644
--- a/components/supervised_user/core/browser/BUILD.gn
+++ b/components/supervised_user/core/browser/BUILD.gn
@@ -94,8 +94,8 @@
   sources = [
     "child_account_service.cc",
     "child_account_service.h",
-    "kids_management_url_checker_client.cc",
-    "kids_management_url_checker_client.h",
+    "kids_chrome_management_url_checker_client.cc",
+    "kids_chrome_management_url_checker_client.h",
     "parental_control_metrics.cc",
     "parental_control_metrics.h",
     "permission_request_creator.h",
@@ -171,7 +171,7 @@
   sources = [
     "api_access_token_fetcher_unittest.cc",
     "child_account_service_unittest.cc",
-    "kids_management_url_checker_client_unittest.cc",
+    "kids_chrome_management_url_checker_client_unittest.cc",
     "list_family_members_service_unittest.cc",
     "parental_control_metrics_unittest.cc",
     "proto_fetcher_unittest.cc",
diff --git a/components/supervised_user/core/browser/kids_management_url_checker_client.cc b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
similarity index 69%
rename from components/supervised_user/core/browser/kids_management_url_checker_client.cc
rename to components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
index 5bbba65e..dc124a4e 100644
--- a/components/supervised_user/core/browser/kids_management_url_checker_client.cc
+++ b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/supervised_user/core/browser/kids_management_url_checker_client.h"
+#include "components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h"
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 
 #include "base/feature_list.h"
@@ -25,6 +26,7 @@
 #include "third_party/protobuf/src/google/protobuf/message_lite.h"
 #include "url/gurl.h"
 
+namespace supervised_user {
 namespace {
 
 using kids_chrome_management::ClassifyUrlResponse;
@@ -41,50 +43,10 @@
   }
 }
 
-// Flips order of arguments so that the sole unbound argument will be the
-// request.
-std::unique_ptr<
-    supervised_user::ProtoFetcher<kids_chrome_management::ClassifyUrlResponse>>
-ClassifyURL(signin::IdentityManager* identity_manager,
-            scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-            const supervised_user::FetcherConfig& config,
-            const kids_chrome_management::ClassifyUrlRequest& request) {
-  return supervised_user::CreateClassifyURLFetcher(
-      *identity_manager, url_loader_factory, request, config);
-}
-
-supervised_user::FetcherConfig GetFetcherConfig() {
-  if (base::FeatureList::IsEnabled(
-          supervised_user::kHighestRequestPriorityForClassifyUrl)) {
-    return supervised_user::kClassifyUrlConfigWithHighestPriority;
-  }
-  return supervised_user::kClassifyUrlConfig;
-}
-
-}  // namespace
-
-KidsManagementURLCheckerClient::KidsManagementURLCheckerClient(
-    signin::IdentityManager* identity_manager,
-    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-    const std::string& country)
-    : safe_search_client_(
-          url_loader_factory,
-          supervised_user::kClassifyUrlConfig.traffic_annotation()),
-      country_(country),
-      fetch_manager_(base::BindRepeating(&ClassifyURL,
-                                         identity_manager,
-                                         url_loader_factory,
-                                         GetFetcherConfig())) {}
-
-KidsManagementURLCheckerClient::~KidsManagementURLCheckerClient() = default;
-
-namespace {
-// Functional equivalent of
-// KidsManagementURLCheckerClient::ConvertResponseCallback
 void OnResponse(
     const GURL& url,
     safe_search_api::URLCheckerClient::ClientCheckCallback client_callback,
-    const supervised_user::ProtoFetcherStatus& status,
+    const ProtoFetcherStatus& status,
     std::unique_ptr<kids_chrome_management::ClassifyUrlResponse>
         classify_url_response) {
   DVLOG(1) << "URL classification = "
@@ -100,21 +62,55 @@
   std::move(client_callback)
       .Run(url, ToSafeSearchClientClassification(classify_url_response.get()));
 }
+
+// Flips order of arguments so that the sole unbound argument will be the
+// request.
+std::unique_ptr<ProtoFetcher<kids_chrome_management::ClassifyUrlResponse>>
+ClassifyURL(signin::IdentityManager* identity_manager,
+            scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+            const FetcherConfig& config,
+            const kids_chrome_management::ClassifyUrlRequest& request) {
+  return CreateClassifyURLFetcher(*identity_manager, url_loader_factory,
+                                  request, config);
+}
+
+FetcherConfig GetFetcherConfig() {
+  if (base::FeatureList::IsEnabled(kHighestRequestPriorityForClassifyUrl)) {
+    return kClassifyUrlConfigWithHighestPriority;
+  }
+  return kClassifyUrlConfig;
+}
+
 }  // namespace
 
-void KidsManagementURLCheckerClient::CheckURL(
+KidsChromeManagementURLCheckerClient::KidsChromeManagementURLCheckerClient(
+    signin::IdentityManager* identity_manager,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+    std::string_view country)
+    : safe_search_client_(url_loader_factory,
+                          kClassifyUrlConfig.traffic_annotation()),
+      country_(country),
+      fetch_manager_(base::BindRepeating(&ClassifyURL,
+                                         identity_manager,
+                                         url_loader_factory,
+                                         GetFetcherConfig())) {}
+
+KidsChromeManagementURLCheckerClient::~KidsChromeManagementURLCheckerClient() =
+    default;
+
+void KidsChromeManagementURLCheckerClient::CheckURL(
     const GURL& url,
     safe_search_api::URLCheckerClient::ClientCheckCallback callback) {
-  auto classify_url_request =
-      std::make_unique<kids_chrome_management::ClassifyUrlRequest>();
-  classify_url_request->set_url(url.spec());
-  classify_url_request->set_region_code(country_);
+  kids_chrome_management::ClassifyUrlRequest request;
+  request.set_url(url.spec());
+  request.set_region_code(country_);
 
-  fetch_manager_.Fetch(*classify_url_request,
+  fetch_manager_.Fetch(request,
                        base::BindOnce(&OnResponse, url, std::move(callback)));
 
-  if (supervised_user::IsShadowKidsApiWithSafeSitesEnabled()) {
+  if (IsShadowKidsApiWithSafeSitesEnabled()) {
     // Actual client is timing the latency in Enterprise.SafeSites.Latency
     safe_search_client_.CheckURL(url, base::DoNothing());
   }
 }
+}  // namespace supervised_user
diff --git a/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h
new file mode 100644
index 0000000..f6be68c
--- /dev/null
+++ b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_CHROME_MANAGEMENT_URL_CHECKER_CLIENT_H_
+#define COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_CHROME_MANAGEMENT_URL_CHECKER_CLIENT_H_
+
+#include <string>
+#include <string_view>
+
+#include "base/memory/scoped_refptr.h"
+#include "components/safe_search_api/safe_search/safe_search_url_checker_client.h"
+#include "components/safe_search_api/url_checker_client.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "components/supervised_user/core/browser/proto/kidschromemanagement_messages.pb.h"
+#include "components/supervised_user/core/browser/proto_fetcher.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+
+class GURL;
+
+namespace supervised_user {
+// This class uses the KidsChromeManagement::ClassifyUrl to check the
+// classification of the content on a given URL and returns the result
+// asynchronously via a callback.
+class KidsChromeManagementURLCheckerClient
+    : public safe_search_api::URLCheckerClient {
+ public:
+  // `country` should be a two-letter country code (ISO 3166-1 alpha-2), e.g.,
+  // "us".
+  KidsChromeManagementURLCheckerClient(
+      signin::IdentityManager* identity_manager,
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
+      std::string_view country);
+
+  KidsChromeManagementURLCheckerClient(
+      const KidsChromeManagementURLCheckerClient&) = delete;
+  KidsChromeManagementURLCheckerClient& operator=(
+      const KidsChromeManagementURLCheckerClient&) = delete;
+
+  ~KidsChromeManagementURLCheckerClient() override;
+
+  // Checks whether an `url` is restricted according to
+  // KidsChromeManagement::ClassifyUrl RPC.
+  //
+  // On failure, the `callback` function is called with `url` as the first
+  // parameter, and UNKNOWN as the second.
+  void CheckURL(const GURL& url, ClientCheckCallback callback) override;
+
+ private:
+  safe_search_api::SafeSearchURLCheckerClient safe_search_client_;
+  const std::string country_;
+
+  ParallelFetchManager<kids_chrome_management::ClassifyUrlRequest,
+                       kids_chrome_management::ClassifyUrlResponse>
+      fetch_manager_;
+};
+
+}  // namespace supervised_user
+
+#endif  // COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_CHROME_MANAGEMENT_URL_CHECKER_CLIENT_H_
diff --git a/components/supervised_user/core/browser/kids_management_url_checker_client_unittest.cc b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
similarity index 85%
rename from components/supervised_user/core/browser/kids_management_url_checker_client_unittest.cc
rename to components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
index bd4ecdc..68767824 100644
--- a/components/supervised_user/core/browser/kids_management_url_checker_client_unittest.cc
+++ b/components/supervised_user/core/browser/kids_chrome_management_url_checker_client_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/supervised_user/core/browser/kids_management_url_checker_client.h"
+#include "components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h"
 
 #include <memory>
 #include <string>
@@ -29,6 +29,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+namespace supervised_user {
 namespace {
 
 using kids_chrome_management::ClassifyUrlResponse;
@@ -52,22 +53,10 @@
   }
 }
 
-class KidsManagementURLCheckerClientTest : public ::testing::Test {
+class KidsChromeManagementURLCheckerClientTest : public ::testing::Test {
  public:
-  KidsManagementURLCheckerClientTest() {
-    shadow_call_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{supervised_user::kShadowKidsApiWithSafeSites},
-        /*disabled_features=*/{});
-  }
-
-  ~KidsManagementURLCheckerClientTest() override {
-      // Since scoped_feature_list_::Init* / scoped_feature_list_.Reset are
-      // stack-based clean-up in the same life-cycle moment.
-      shadow_call_feature_list_.Reset();
-  }
-
   void SetUp() override {
-    url_classifier_ = std::make_unique<KidsManagementURLCheckerClient>(
+    url_classifier_ = std::make_unique<KidsChromeManagementURLCheckerClient>(
         identity_test_env_.identity_manager(),
         test_url_loader_factory_.GetSafeWeakWrapper(), "us");
   }
@@ -94,8 +83,7 @@
         std::string(kSafeSitesEndpoint),
         base::StringPrintf(R"json(
           {"displayClassification": "%s"}
-        )json",
-                           ConvertToString(classification).c_str()));
+        )json",ConvertToString(classification).c_str()));
   }
 
   void SimulateMalformedResponse() {
@@ -135,7 +123,7 @@
   void StartCheckUrl(base::StringPiece url) {
     url_classifier_->CheckURL(
         GURL(url),
-        base::BindOnce(&KidsManagementURLCheckerClientTest::OnCheckDone,
+        base::BindOnce(&KidsChromeManagementURLCheckerClientTest::OnCheckDone,
                        base::Unretained(this)));
   }
 
@@ -144,11 +132,11 @@
 
  private:
   signin::IdentityTestEnvironment identity_test_env_;
-  std::unique_ptr<KidsManagementURLCheckerClient> url_classifier_;
-  base::test::ScopedFeatureList shadow_call_feature_list_;
+  std::unique_ptr<KidsChromeManagementURLCheckerClient> url_classifier_;
+  base::test::ScopedFeatureList shadow_call_feature_list_{kShadowKidsApiWithSafeSites};
 };
 
-TEST_F(KidsManagementURLCheckerClientTest, UrlAllowed) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, UrlAllowed) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -162,7 +150,7 @@
   SimulateKidsApiResponse(kids_chrome_management::ClassifyUrlResponse::ALLOWED);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, HistogramsAreEmitted) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, HistogramsAreEmitted) {
   base::HistogramTester histogram_tester;
   MakePrimaryAccountAvailable();
 
@@ -183,7 +171,7 @@
                                     /*expected_count(grew by)*/ 1);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, UrlRestricted) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, UrlRestricted) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -198,7 +186,7 @@
       kids_chrome_management::ClassifyUrlResponse::RESTRICTED);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, AccessTokenError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, AccessTokenError) {
   EXPECT_CALL(*this,
               OnCheckDone(GURL("http://example.com"),
                           safe_search_api::ClientClassification::kUnknown));
@@ -209,7 +197,7 @@
   SimulateSafeSitesResponse(safe_search_api::ClientClassification::kAllowed);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, NetworkError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, NetworkError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -223,7 +211,7 @@
   SimulateNetworkError(net::ERR_UNEXPECTED);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, HttpError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, HttpError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -237,7 +225,7 @@
   SimulateHttpError(net::HTTP_BAD_GATEWAY);
 }
 
-TEST_F(KidsManagementURLCheckerClientTest, ServiceError) {
+TEST_F(KidsChromeManagementURLCheckerClientTest, ServiceError) {
   MakePrimaryAccountAvailable();
 
   EXPECT_CALL(*this,
@@ -251,7 +239,7 @@
   SimulateMalformedResponse();
 }
 
-TEST_F(KidsManagementURLCheckerClientTest,
+TEST_F(KidsChromeManagementURLCheckerClientTest,
        PendingRequestsAreCanceledWhenClientIsDestroyed) {
   EXPECT_CALL(*this, OnCheckDone(_, _)).Times(0);
 
@@ -262,3 +250,4 @@
   task_environment_.RunUntilIdle();
 }
 }  // namespace
+}  // namespace supervised_user
diff --git a/components/supervised_user/core/browser/kids_management_url_checker_client.h b/components/supervised_user/core/browser/kids_management_url_checker_client.h
deleted file mode 100644
index 3413bcb8..0000000
--- a/components/supervised_user/core/browser/kids_management_url_checker_client.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_MANAGEMENT_URL_CHECKER_CLIENT_H_
-#define COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_MANAGEMENT_URL_CHECKER_CLIENT_H_
-
-#include <string>
-
-#include "base/memory/scoped_refptr.h"
-#include "components/safe_search_api/safe_search/safe_search_url_checker_client.h"
-#include "components/safe_search_api/url_checker_client.h"
-#include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/supervised_user/core/browser/proto/kidschromemanagement_messages.pb.h"
-#include "components/supervised_user/core/browser/proto_fetcher.h"
-#include "services/network/public/cpp/shared_url_loader_factory.h"
-
-class GURL;
-
-// TODO(crbug.com/988428): Change comments to use KidsChromeManagement instead
-// of KidsManagement when migration is complete.
-
-// This class uses the KidsManagement ClassifyUrl to check the classification
-// of the content on a given URL and returns the result asynchronously
-// via a callback.
-class KidsManagementURLCheckerClient
-    : public safe_search_api::URLCheckerClient {
- public:
-  // |country| should be a two-letter country code (ISO 3166-1 alpha-2), e.g.,
-  // "us".
-  KidsManagementURLCheckerClient(
-      signin::IdentityManager* identity_manager,
-      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
-      const std::string& country);
-
-  KidsManagementURLCheckerClient(const KidsManagementURLCheckerClient&) =
-      delete;
-  KidsManagementURLCheckerClient& operator=(
-      const KidsManagementURLCheckerClient&) = delete;
-
-  ~KidsManagementURLCheckerClient() override;
-
-  // Checks whether an |url| is restricted according to KidsManagement
-  // ClassifyUrl RPC.
-  //
-  // On failure, the |callback| function is called with |url| as the first
-  // parameter, and UNKNOWN as the second.
-  void CheckURL(const GURL& url, ClientCheckCallback callback) override;
-
- private:
-  safe_search_api::SafeSearchURLCheckerClient safe_search_client_;
-  const std::string country_;
-
-  supervised_user::ParallelFetchManager<
-      kids_chrome_management::ClassifyUrlRequest,
-      kids_chrome_management::ClassifyUrlResponse>
-      fetch_manager_;
-};
-
-#endif  // COMPONENTS_SUPERVISED_USER_CORE_BROWSER_KIDS_MANAGEMENT_URL_CHECKER_CLIENT_H_
diff --git a/components/supervised_user/core/browser/supervised_user_url_filter.cc b/components/supervised_user/core/browser/supervised_user_url_filter.cc
index 8ddff93..57da2036 100644
--- a/components/supervised_user/core/browser/supervised_user_url_filter.cc
+++ b/components/supervised_user/core/browser/supervised_user_url_filter.cc
@@ -20,7 +20,7 @@
 #include "base/strings/string_util.h"
 #include "base/task/thread_pool.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/supervised_user/core/browser/kids_management_url_checker_client.h"
+#include "components/supervised_user/core/browser/kids_chrome_management_url_checker_client.h"
 #include "components/supervised_user/core/common/supervised_user_constants.h"
 #include "components/supervised_user/core/common/supervised_user_utils.h"
 #include "components/url_matcher/url_util.h"
@@ -682,7 +682,7 @@
   std::string country = service_delegate_->GetCountryCode();
 
   std::unique_ptr<safe_search_api::URLCheckerClient> url_checker_client =
-      std::make_unique<KidsManagementURLCheckerClient>(
+      std::make_unique<KidsChromeManagementURLCheckerClient>(
           identity_manager, url_loader_factory, country);
   async_url_checker_ = std::make_unique<safe_search_api::URLChecker>(
       std::move(url_checker_client));
diff --git a/components/sync/engine/model_type_worker.cc b/components/sync/engine/model_type_worker.cc
index c3484499..70a50f1 100644
--- a/components/sync/engine/model_type_worker.cc
+++ b/components/sync/engine/model_type_worker.cc
@@ -267,6 +267,7 @@
       !invitation.sender_info().has_cross_user_sharing_public_key()) {
     LogCrossUserSharingDecryptionResult(
         CrossUserSharingDecryptionResult::kInvitationMissingFields);
+    DLOG(ERROR) << "The invitation is missing required fields";
     return false;
   }
 
@@ -281,6 +282,7 @@
   if (!decrypted) {
     LogCrossUserSharingDecryptionResult(
         CrossUserSharingDecryptionResult::kFailedToDecryptInvitation);
+    DLOG(ERROR) << "Failed to decrypt the invitation";
     return false;
   }
 
@@ -288,6 +290,7 @@
                                                    decrypted->size())) {
     LogCrossUserSharingDecryptionResult(
         CrossUserSharingDecryptionResult::kFailedToParseDecryptedInvitation);
+    DLOG(ERROR) << "Failed to parse the decrypted invitation";
     return false;
   }
 
diff --git a/components/sync/nigori/cross_user_sharing_keys.cc b/components/sync/nigori/cross_user_sharing_keys.cc
index c8fbbca..20b093d 100644
--- a/components/sync/nigori/cross_user_sharing_keys.cc
+++ b/components/sync/nigori/cross_user_sharing_keys.cc
@@ -74,7 +74,9 @@
 
 CrossUserSharingKeys CrossUserSharingKeys::Clone() const {
   CrossUserSharingKeys copy;
-  copy.AddAllUnknownKeysFrom(*this);
+  for (const auto& [version, key_pair] : key_pairs_map_) {
+    copy.AddKeyPair(CloneKeyPair(key_pair), version);
+  }
   return copy;
 }
 
@@ -86,13 +88,6 @@
   return key_pairs_map_.contains(key_pair_version);
 }
 
-void CrossUserSharingKeys::AddAllUnknownKeysFrom(
-    const CrossUserSharingKeys& other) {
-  for (const auto& [public_key, key_pair] : other.key_pairs_map_) {
-    key_pairs_map_.emplace(public_key, CloneKeyPair(key_pair));
-  }
-}
-
 bool CrossUserSharingKeys::AddKeyPairFromProto(
     const sync_pb::CrossUserSharingPrivateKey& key) {
   std::vector<uint8_t> private_key(key.x25519_private_key().begin(),
@@ -111,6 +106,8 @@
 void CrossUserSharingKeys::AddKeyPair(
     CrossUserSharingPublicPrivateKeyPair key_pair,
     uint32_t version) {
+  // TODO(crbug.com/1511180): verify that the following emplace does not cause
+  // key loss.
   key_pairs_map_.emplace(version, std::move(key_pair));
 }
 
diff --git a/components/sync/nigori/cross_user_sharing_keys.h b/components/sync/nigori/cross_user_sharing_keys.h
index 1b6f883..1cb742bc 100644
--- a/components/sync/nigori/cross_user_sharing_keys.h
+++ b/components/sync/nigori/cross_user_sharing_keys.h
@@ -37,10 +37,6 @@
   size_t size() const;
   bool HasKeyPair(const uint32_t key_version) const;
 
-  // Merges all keys from another CrossUserSharingKeys object, which means
-  // adding all keys that we don't know about.
-  void AddAllUnknownKeysFrom(const CrossUserSharingKeys& other);
-
   // Adds a Public-private key-pair associated with |version|.
   void AddKeyPair(CrossUserSharingPublicPrivateKeyPair key_pair,
                   uint32_t version);
@@ -61,7 +57,7 @@
   CrossUserSharingKeys();
 
   // Public-private key-pairs we know about, mapped by version.
-  std::map<uint32_t, const CrossUserSharingPublicPrivateKeyPair> key_pairs_map_;
+  std::map<uint32_t, CrossUserSharingPublicPrivateKeyPair> key_pairs_map_;
 };
 
 }  // namespace syncer
diff --git a/components/sync/nigori/cryptographer_impl.cc b/components/sync/nigori/cryptographer_impl.cc
index d7dbd95..c584419 100644
--- a/components/sync/nigori/cryptographer_impl.cc
+++ b/components/sync/nigori/cryptographer_impl.cc
@@ -88,9 +88,8 @@
   key_bag_.AddAllUnknownKeysFrom(key_bag);
 }
 
-void CryptographerImpl::EmplaceCrossUserSharingKeysFrom(
-    const CrossUserSharingKeys& keys) {
-  cross_user_sharing_keys_.AddAllUnknownKeysFrom(keys);
+void CryptographerImpl::ReplaceCrossUserSharingKeys(CrossUserSharingKeys keys) {
+  cross_user_sharing_keys_ = std::move(keys);
 }
 
 void CryptographerImpl::SelectDefaultEncryptionKey(
diff --git a/components/sync/nigori/cryptographer_impl.h b/components/sync/nigori/cryptographer_impl.h
index 4ad8b301..df3134c 100644
--- a/components/sync/nigori/cryptographer_impl.h
+++ b/components/sync/nigori/cryptographer_impl.h
@@ -63,8 +63,8 @@
   // Does NOT set or change the default encryption key.
   void EmplaceKeysFrom(const NigoriKeyBag& key_bag);
 
-  // Adds all keys from |keys| that weren't previously known.
-  void EmplaceCrossUserSharingKeysFrom(const CrossUserSharingKeys& keys);
+  // Drops any pre-existing key pairs and adds all keys from |keys|.
+  void ReplaceCrossUserSharingKeys(CrossUserSharingKeys keys);
 
   // Adds the given Public-private key-pair associated with |version|.
   void EmplaceKeyPair(CrossUserSharingPublicPrivateKeyPair key_pair,
diff --git a/components/sync/nigori/cryptographer_impl_unittest.cc b/components/sync/nigori/cryptographer_impl_unittest.cc
index dd70630a..dfc6d04 100644
--- a/components/sync/nigori/cryptographer_impl_unittest.cc
+++ b/components/sync/nigori/cryptographer_impl_unittest.cc
@@ -269,7 +269,7 @@
   EXPECT_TRUE(cryptographer->HasKeyPair(0));
 }
 
-TEST(CryptographerImplTest, ShouldEmplaceCrossUserSharingKeysFrom) {
+TEST(CryptographerImplTest, ShouldReplaceCrossUserSharingKeys) {
   std::unique_ptr<CryptographerImpl> cryptographer =
       CryptographerImpl::CreateEmpty();
   ASSERT_THAT(cryptographer, NotNull());
@@ -280,12 +280,43 @@
   keys.AddKeyPair(CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair(),
                   1);
 
-  cryptographer->EmplaceCrossUserSharingKeysFrom(keys);
+  cryptographer->ReplaceCrossUserSharingKeys(std::move(keys));
 
   EXPECT_TRUE(cryptographer->HasKeyPair(0));
   EXPECT_TRUE(cryptographer->HasKeyPair(1));
 }
 
+TEST(CryptographerImplTest, ShouldOverwritePreexistingKeys) {
+  std::unique_ptr<CryptographerImpl> cryptographer =
+      CryptographerImpl::CreateEmpty();
+  CrossUserSharingKeys old_keys = CrossUserSharingKeys::CreateEmpty();
+  old_keys.AddKeyPair(
+      CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair(),
+      /*version=*/0);
+  old_keys.AddKeyPair(
+      CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair(),
+      /*version=*/1);
+  cryptographer->ReplaceCrossUserSharingKeys(old_keys.Clone());
+  ASSERT_TRUE(cryptographer->HasKeyPair(/*version=*/0));
+  ASSERT_TRUE(cryptographer->HasKeyPair(/*version=*/1));
+
+  // Generate a new key pair and replace the pre-existing one with the same
+  // version. The version 1 should also disappear.
+  CrossUserSharingKeys new_keys = CrossUserSharingKeys::CreateEmpty();
+  new_keys.AddKeyPair(
+      CrossUserSharingPublicPrivateKeyPair::GenerateNewKeyPair(),
+      /*version=*/0);
+  cryptographer->ReplaceCrossUserSharingKeys(new_keys.Clone());
+  ASSERT_TRUE(cryptographer->HasKeyPair(/*version=*/0));
+  ASSERT_FALSE(cryptographer->HasKeyPair(/*version=*/1));
+  EXPECT_EQ(cryptographer->GetCrossUserSharingKeyPair(/*version=*/0)
+                .GetRawPrivateKey(),
+            new_keys.GetKeyPair(/*version=*/0).GetRawPrivateKey());
+  EXPECT_NE(cryptographer->GetCrossUserSharingKeyPair(/*version=*/0)
+                .GetRawPrivateKey(),
+            old_keys.GetKeyPair(/*version=*/0).GetRawPrivateKey());
+}
+
 TEST(CryptographerImplTest, ShouldEncryptAndDecryptForCrossUserSharing) {
   std::unique_ptr<CryptographerImpl> cryptographer_sender =
       CryptographerImpl::CreateEmpty();
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index de72f4ab..55680cc0 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -236,6 +236,7 @@
 
 // These values are persisted to UMA. Entries should not be renumbered and
 // numeric values should never be reused.
+// TODO(crbug.com/1511180): add a bucket for the pending keys state.
 enum class CrossUserSharingKeyPairStateForUMA {
   kValidKeyPair = 0,
   kPublicKeyNotInitialized = 1,
@@ -380,6 +381,10 @@
 
   if (base::FeatureList::IsEnabled(kSharingOfferKeyPairBootstrap) &&
       !state_.cross_user_sharing_public_key.has_value()) {
+    // Generate a new key pair if there is no public key in the local state.
+    // Note that this can trigger a key pair generation if the current client
+    // has been just upgraded from the older version (so it wasn't aware of key
+    // pairs). Other clients are expected to apply the newly generated key pair.
     QueuePendingLocalCommit(
         PendingLocalNigoriCommit::
             ForCrossUserSharingPublicPrivateKeyInitializer());
@@ -888,8 +893,12 @@
       state_.cross_user_sharing_key_pair_version = absl::nullopt;
       state_.cross_user_sharing_public_key = absl::nullopt;
     } else if (state_.cross_user_sharing_key_pair_version.has_value()) {
-      state_.cryptographer->EmplaceCrossUserSharingKeysFrom(
-          new_cross_user_sharing_keys);
+      // Use the keys from the server and replace any pre-existing ones (so in
+      // case of conflict the server wins). One of cases when this can happen is
+      // when one of older clients is upgraded to a newer version and generated
+      // a new key pair because it wasn't aware of the previous key pair.
+      state_.cryptographer->ReplaceCrossUserSharingKeys(
+          std::move(new_cross_user_sharing_keys));
       state_.cryptographer->SelectDefaultCrossUserSharingKey(
           state_.cross_user_sharing_key_pair_version.value());
     }
diff --git a/components/sync/service/model_load_manager.cc b/components/sync/service/model_load_manager.cc
index 217b27a..f1dc021 100644
--- a/components/sync/service/model_load_manager.cc
+++ b/components/sync/service/model_load_manager.cc
@@ -54,8 +54,11 @@
       // Controllers in a FAILED state or with preconditions not met should have
       // been filtered out by the DataTypeManager.
       CHECK_NE(dtc->state(), DataTypeController::FAILED);
-      CHECK_EQ(dtc->GetPreconditionState(),
-               DataTypeController::PreconditionState::kPreconditionsMet);
+      // TODO(crbug.com/1514430): consider removing the following CHECK because
+      // data types can change their state and notify DataTypeManager later.
+      DUMP_WILL_BE_CHECK_EQ(
+          dtc->GetPreconditionState(),
+          DataTypeController::PreconditionState::kPreconditionsMet);
       preferred_types_without_errors_.Put(type);
     }
   }
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn
index 4546d2e5..7c3e248 100644
--- a/components/variations/BUILD.gn
+++ b/components/variations/BUILD.gn
@@ -52,6 +52,8 @@
     "fake_crash.h",
     "hashing.cc",
     "hashing.h",
+    "limited_entropy_mode_gate.cc",
+    "limited_entropy_mode_gate.h",
     "metrics.cc",
     "metrics.h",
     "platform_field_trials.h",
diff --git a/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc
index a50523fb..443819d5 100644
--- a/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc
+++ b/components/variations/cros_evaluate_seed/cros_variations_field_trial_creator.cc
@@ -16,11 +16,13 @@
 CrOSVariationsFieldTrialCreator::CrOSVariationsFieldTrialCreator(
     VariationsServiceClient* client,
     std::unique_ptr<VariationsSeedStore> seed_store)
-    : VariationsFieldTrialCreatorBase(client,
-                                      std::move(seed_store),
-                                      base::BindOnce([](PrefService*) {
-                                        return std::string(kFakeLocale);
-                                      })) {}
+    : VariationsFieldTrialCreatorBase(
+          client,
+          std::move(seed_store),
+          base::BindOnce([](PrefService*) { return std::string(kFakeLocale); }),
+          // The limited entropy synthetic trial will not be registered for this
+          // purpose.
+          /*limited_entropy_synthetic_trial=*/nullptr) {}
 
 CrOSVariationsFieldTrialCreator::~CrOSVariationsFieldTrialCreator() = default;
 
diff --git a/components/variations/limited_entropy_mode_gate.cc b/components/variations/limited_entropy_mode_gate.cc
new file mode 100644
index 0000000..a3c4fc19
--- /dev/null
+++ b/components/variations/limited_entropy_mode_gate.cc
@@ -0,0 +1,25 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/variations/limited_entropy_mode_gate.h"
+
+namespace variations {
+
+namespace {
+bool g_is_limited_entropy_mode_enabled_for_testing = false;
+}
+
+bool IsLimitedEntropyModeEnabled() {
+  if (g_is_limited_entropy_mode_enabled_for_testing) {
+    return true;
+  }
+  // TODO(crbug.com/1511779): Enable limited entropy mode by channel.
+  return false;
+}
+
+void EnableLimitedEntropyModeForTesting() {
+  g_is_limited_entropy_mode_enabled_for_testing = true;
+}
+
+}  // namespace variations
diff --git a/components/variations/limited_entropy_mode_gate.h b/components/variations/limited_entropy_mode_gate.h
new file mode 100644
index 0000000..899863c
--- /dev/null
+++ b/components/variations/limited_entropy_mode_gate.h
@@ -0,0 +1,23 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VARIATIONS_LIMITED_ENTROPY_MODE_GATE_H_
+#define COMPONENTS_VARIATIONS_LIMITED_ENTROPY_MODE_GATE_H_
+
+#include "base/component_export.h"
+
+namespace variations {
+
+// Returns true iff this client is eligible to randomize field trials within a
+// layer with LIMITED entropy mode, or if the client has called
+// EnableLimitedEntropyModeForTesting().
+COMPONENT_EXPORT(VARIATIONS) bool IsLimitedEntropyModeEnabled();
+
+// Enables the client to randomize field trials within a layer with LIMITED
+// entropy mode. For testing purposes only.
+COMPONENT_EXPORT(VARIATIONS) void EnableLimitedEntropyModeForTesting();
+
+}  // namespace variations
+
+#endif  // COMPONENTS_VARIATIONS_LIMITED_ENTROPY_MODE_GATE_H_
diff --git a/components/variations/service/variations_field_trial_creator.cc b/components/variations/service/variations_field_trial_creator.cc
index e7fead5..1d659e1 100644
--- a/components/variations/service/variations_field_trial_creator.cc
+++ b/components/variations/service/variations_field_trial_creator.cc
@@ -26,13 +26,15 @@
 VariationsFieldTrialCreator::VariationsFieldTrialCreator(
     VariationsServiceClient* client,
     std::unique_ptr<VariationsSeedStore> seed_store,
-    const UIStringOverrider& ui_string_overrider)
+    const UIStringOverrider& ui_string_overrider,
+    LimitedEntropySyntheticTrial* limited_entropy_synthetic_trial)
     : VariationsFieldTrialCreatorBase(
           client,
           std::move(seed_store),
           base::BindOnce([](PrefService* local_state) {
             return language::GetApplicationLocale(local_state);
-          })),
+          }),
+          limited_entropy_synthetic_trial),
       ui_string_overrider_(ui_string_overrider) {}
 
 VariationsFieldTrialCreator::~VariationsFieldTrialCreator() {}
diff --git a/components/variations/service/variations_field_trial_creator.h b/components/variations/service/variations_field_trial_creator.h
index 1dcf22af..c8a79521 100644
--- a/components/variations/service/variations_field_trial_creator.h
+++ b/components/variations/service/variations_field_trial_creator.h
@@ -16,6 +16,7 @@
 
 namespace variations {
 
+class LimitedEntropySyntheticTrial;
 class VariationsServiceClient;
 
 // Used to set up field trials based on stored variations seed data.
@@ -23,9 +24,11 @@
  public:
   // Caller is responsible for ensuring that objects passed to the constructor
   // stay valid for the lifetime of this object.
-  VariationsFieldTrialCreator(VariationsServiceClient* client,
-                              std::unique_ptr<VariationsSeedStore> seed_store,
-                              const UIStringOverrider& ui_string_overrider);
+  VariationsFieldTrialCreator(
+      VariationsServiceClient* client,
+      std::unique_ptr<VariationsSeedStore> seed_store,
+      const UIStringOverrider& ui_string_overrider,
+      LimitedEntropySyntheticTrial* limited_entropy_synthetic_trial);
 
   VariationsFieldTrialCreator(const VariationsFieldTrialCreator&) = delete;
   VariationsFieldTrialCreator& operator=(const VariationsFieldTrialCreator&) =
diff --git a/components/variations/service/variations_field_trial_creator_base.cc b/components/variations/service/variations_field_trial_creator_base.cc
index eccaccbd..a7e250e 100644
--- a/components/variations/service/variations_field_trial_creator_base.cc
+++ b/components/variations/service/variations_field_trial_creator_base.cc
@@ -35,6 +35,7 @@
 #include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/field_trial_config/field_trial_util.h"
+#include "components/variations/limited_entropy_mode_gate.h"
 #include "components/variations/platform_field_trials.h"
 #include "components/variations/pref_names.h"
 #include "components/variations/proto/variations_seed.pb.h"
@@ -161,6 +162,16 @@
       /*is_extended_safe_mode=*/true);
 }
 
+// Returns true iff the given seed contains a layer with LIMITED entropy mode.
+bool ContainsLimitedEntropyLayer(const VariationsSeed& seed) {
+  for (const Layer& layer_proto : seed.layers()) {
+    if (layer_proto.entropy_mode() == Layer::LIMITED) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace
 
 BASE_FEATURE(kForceFieldTrialSetupCrashForTesting,
@@ -188,13 +199,15 @@
 VariationsFieldTrialCreatorBase::VariationsFieldTrialCreatorBase(
     VariationsServiceClient* client,
     std::unique_ptr<VariationsSeedStore> seed_store,
-    base::OnceCallback<std::string(PrefService*)> locale_cb)
+    base::OnceCallback<std::string(PrefService*)> locale_cb,
+    LimitedEntropySyntheticTrial* limited_entropy_synthetic_trial)
     : client_(client),
       seed_store_(std::move(seed_store)),
       create_trials_from_seed_called_(false),
       application_locale_(std::move(locale_cb).Run(seed_store_->local_state())),
       has_platform_override_(false),
-      platform_override_(Study::PLATFORM_WINDOWS) {}
+      platform_override_(Study::PLATFORM_WINDOWS),
+      limited_entropy_synthetic_trial_(limited_entropy_synthetic_trial) {}
 
 VariationsFieldTrialCreatorBase::~VariationsFieldTrialCreatorBase() = default;
 
@@ -645,6 +658,14 @@
   RecordVariationsSeedUsage(run_in_safe_mode ? SeedUsage::kSafeSeedUsed
                                              : SeedUsage::kRegularSeedUsed);
 
+  // Register group membership for the limited entropy synthetic trial if the
+  // required parameters are non null, and a LIMITED entropy layer is in the
+  // seed.
+  if (IsLimitedEntropyModeEnabled() && limited_entropy_synthetic_trial_ &&
+      ContainsLimitedEntropyLayer(seed)) {
+    client_->RegisterLimitedEntropySyntheticTrial(
+        limited_entropy_synthetic_trial_->GetGroupName());
+  }
   // Note that passing base::Unretained(this) below is safe because the callback
   // is executed synchronously. It is not possible to pass UIStringOverrider
   // directly to VariationsSeedProcessor (which is in components/variations and
diff --git a/components/variations/service/variations_field_trial_creator_base.h b/components/variations/service/variations_field_trial_creator_base.h
index 5d9aae2a..748683a8 100644
--- a/components/variations/service/variations_field_trial_creator_base.h
+++ b/components/variations/service/variations_field_trial_creator_base.h
@@ -36,7 +36,9 @@
 #include "components/variations/proto/study.pb.h"
 #include "components/variations/seed_response.h"
 #include "components/variations/service/buildflags.h"
+#include "components/variations/service/limited_entropy_synthetic_trial.h"
 #include "components/variations/service/safe_seed_manager.h"
+#include "components/variations/service/variations_service_client.h"
 #include "components/variations/variations_seed_store.h"
 #include "components/version_info/channel.h"
 
@@ -110,10 +112,16 @@
   // Caller is responsible for ensuring that the VariationsServiceClient passed
   // to the constructor stays valid for the lifetime of this object.
   // |locale_cb| computes the locale, given a PrefService for local_state.
+  //
+  // The client will be registered to the limited entropy synthetic trial iff
+  // |limited_entropy_synthetic_trial| is not null. Caller is responsible for
+  // ensuring |limited_entropy_synthetic_trial| stays valid for the lifetime of
+  // this object.
   VariationsFieldTrialCreatorBase(
       VariationsServiceClient* client,
       std::unique_ptr<VariationsSeedStore> seed_store,
-      base::OnceCallback<std::string(PrefService*)> locale_cb);
+      base::OnceCallback<std::string(PrefService*)> locale_cb,
+      LimitedEntropySyntheticTrial* limited_entropy_synthetic_trial);
 
   VariationsFieldTrialCreatorBase(const VariationsFieldTrialCreatorBase&) =
       delete;
@@ -299,6 +307,9 @@
   // These strings are cached before the resource bundle is initialized.
   std::unordered_map<int, std::u16string> overridden_strings_map_;
 
+  // Configurations related to the limited entropy synthetic trial.
+  raw_ptr<LimitedEntropySyntheticTrial> limited_entropy_synthetic_trial_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
diff --git a/components/variations/service/variations_field_trial_creator_unittest.cc b/components/variations/service/variations_field_trial_creator_unittest.cc
index b77913dd..b63f2ad5 100644
--- a/components/variations/service/variations_field_trial_creator_unittest.cc
+++ b/components/variations/service/variations_field_trial_creator_unittest.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <utility>
 
+#include "base/base64.h"
 #include "base/base_switches.h"
 #include "base/build_time.h"
 #include "base/command_line.h"
@@ -39,11 +40,13 @@
 #include "components/metrics/test/test_enabled_state_provider.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/variations/field_trial_config/field_trial_util.h"
+#include "components/variations/limited_entropy_mode_gate.h"
 #include "components/variations/platform_field_trials.h"
 #include "components/variations/pref_names.h"
 #include "components/variations/proto/variations_seed.pb.h"
 #include "components/variations/scoped_variations_ids_provider.h"
 #include "components/variations/service/buildflags.h"
+#include "components/variations/service/limited_entropy_synthetic_trial.h"
 #include "components/variations/service/safe_seed_manager.h"
 #include "components/variations/service/variations_service.h"
 #include "components/variations/service/variations_service_client.h"
@@ -56,6 +59,7 @@
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/google/compression_utils.h"
 
 #if BUILDFLAG(IS_ANDROID)
 #include "components/variations/seed_response.h"
@@ -108,6 +112,38 @@
   return seed;
 }
 
+// Returns a test seed that contains a single study,
+// "UMA-Uniformity-Trial-10-Percent", which has a single experiment, "abc", with
+// probability weight 100. The study references the 100% slot of a LIMITED
+// entropy layer.
+VariationsSeed CreateTestSeedWithLimitedEntropyLayer() {
+  VariationsSeed seed;
+  seed.set_serial_number(kTestSeedSerialNumber);
+
+  auto* layer = seed.add_layers();
+  layer->set_id(1);
+  layer->set_num_slots(100);
+  layer->set_entropy_mode(Layer::LIMITED);
+
+  auto* layer_member = layer->add_members();
+  layer_member->set_id(1);
+  auto* slot = layer_member->add_slots();
+  slot->set_start(0);
+  slot->set_end(99);
+
+  Study* study = seed.add_study();
+  study->set_name(kTestSeedStudyName);
+  auto* layer_member_reference = study->mutable_layer();
+  layer_member_reference->set_layer_id(1);
+  layer_member_reference->set_layer_member_id(1);
+
+  Study_Experiment* experiment = study->add_experiment();
+  experiment->set_name(kTestSeedExperimentName);
+  experiment->set_probability_weight(kTestSeedExperimentProbability);
+
+  return seed;
+}
+
 // Returns a seed with simple test data. The seed has a single study,
 // "UMA-Uniformity-Trial-10-Percent", which has a single experiment,
 // "abc.safe", with probability weight 100.
@@ -122,6 +158,14 @@
   return seed;
 }
 
+// Returns a hex string of the GZipped, base64 encoded, and serialized seed.
+std::string GZipAndB64EncodeToHexString(const VariationsSeed& seed) {
+  auto serialized = seed.SerializeAsString();
+  std::string compressed;
+  compression::GzipCompress(serialized, &compressed);
+  return base::Base64Encode(compressed);
+}
+
 // A base::Time instance representing a time in the distant past. Here, it would
 // return the start for epoch in Unix-like system (Jan 1, 1970).
 base::Time DistantPast() {
@@ -210,6 +254,14 @@
   bool IsEnterprise() override { return false; }
   void RemoveGoogleGroupsFromPrefsForDeletedProfiles(
       PrefService* local_state) override {}
+  // TODO(crbug.com/1508150): Remove once the trial has wrapped up.
+  void RegisterLimitedEntropySyntheticTrial(
+      std::string_view group_name) override {
+    limited_entropy_synthetic_trial_group_ = std::string(group_name);
+  }
+  std::string_view GetLimitedEntropySyntheticTrialGroupName() {
+    return limited_entropy_synthetic_trial_group_;
+  }
 
  private:
   // VariationsServiceClient:
@@ -218,6 +270,7 @@
   }
 
   std::string restrict_parameter_;
+  std::string limited_entropy_synthetic_trial_group_;
 };
 
 class MockVariationsServiceClient : public TestVariationsServiceClient {
@@ -278,10 +331,12 @@
       const base::FilePath user_data_dir = base::FilePath(),
       metrics::StartupVisibility startup_visibility =
           metrics::StartupVisibility::kUnknown)
-      : VariationsFieldTrialCreator(client,
-                                    // Pass a VariationsSeedStore to base class.
-                                    CreateSeedStore(local_state),
-                                    UIStringOverrider()),
+      : VariationsFieldTrialCreator(
+            client,
+            // Pass a VariationsSeedStore to base class.
+            CreateSeedStore(local_state),
+            UIStringOverrider(),
+            /*limited_entropy_synthetic_trial=*/nullptr),
         enabled_state_provider_(/*consent=*/true, /*enabled=*/true),
         // Instead, use a TestVariationsSeedStore as the member variable.
         seed_store_(local_state),
@@ -800,7 +855,8 @@
   TestVariationsServiceClient variations_service_client;
   auto seed_store = CreateSeedStore(local_state());
   VariationsFieldTrialCreator field_trial_creator(
-      &variations_service_client, std::move(seed_store), UIStringOverrider());
+      &variations_service_client, std::move(seed_store), UIStringOverrider(),
+      /*limited_entropy_synthetic_trial=*/nullptr);
   metrics::TestEnabledStateProvider enabled_state_provider(
       /*consent=*/true,
       /*enabled=*/true);
@@ -850,7 +906,8 @@
       /*signature_verification_enabled=*/false,
       std::make_unique<VariationsSafeSeedStoreLocalState>(local_state()));
   VariationsFieldTrialCreator field_trial_creator(
-      &variations_service_client, std::move(seed_store), UIStringOverrider());
+      &variations_service_client, std::move(seed_store), UIStringOverrider(),
+      /*limited_entropy_synthetic_trial=*/nullptr);
 
   metrics::TestEnabledStateProvider enabled_state_provider(/*consent=*/true,
                                                            /*enabled=*/true);
@@ -1374,6 +1431,100 @@
 
 namespace {
 
+struct TestCase {
+  bool is_limited_layer_in_seed;
+  bool is_limited_entropy_synthetic_trial_applicable;
+  std::string test_name;
+  bool is_group_registration_expected;
+};
+
+const TestCase kTestCases[] = {
+    {true, true, "ApplicableClientWithLimitedLayer", true},
+    {true, false, "NonApplicableClientWithLimitedLayer", false},
+    {false, true, "ApplicableClientWithoutLimitedLayer", false},
+    {false, false, "NonApplicableClientWithoutLimitedLayer", false},
+};
+
+class LimitedEntropySyntheticTrialGroupRegistrationTest
+    : public FieldTrialCreatorTest,
+      public ::testing::WithParamInterface<TestCase> {
+ public:
+  LimitedEntropySyntheticTrialGroupRegistrationTest() {
+    EnableLimitedEntropyModeForTesting();
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(,
+                         LimitedEntropySyntheticTrialGroupRegistrationTest,
+                         ::testing::ValuesIn(kTestCases),
+                         [](const ::testing::TestParamInfo<TestCase>& info) {
+                           return info.param.test_name;
+                         });
+
+TEST_P(LimitedEntropySyntheticTrialGroupRegistrationTest,
+       RegisterGroupMembership) {
+  const TestCase test_case = GetParam();
+
+  VariationsSeed seed;
+  if (test_case.is_limited_layer_in_seed) {
+    // Sets up a test seed with a LIMITED entropy layer.
+    seed = CreateTestSeedWithLimitedEntropyLayer();
+  } else {
+    // Sets up a test seed without a LIMITED entropy layer.
+    seed = CreateTestSeed();
+  }
+  auto encoded_and_compressed = GZipAndB64EncodeToHexString(seed);
+  local_state()->SetString(prefs::kVariationsCompressedSeed,
+                           encoded_and_compressed);
+
+  // Allows and writes an empty signature for the test seed.
+  base::CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kAcceptEmptySeedSignatureForTesting);
+  local_state()->SetString(prefs::kVariationsSeedSignature, "");
+
+  // Sets up dependencies and mocks.
+  TestVariationsServiceClient variations_service_client;
+  LimitedEntropySyntheticTrial limited_entropy_synthetic_trial(local_state());
+  auto seed_store = CreateSeedStore(local_state());
+  VariationsFieldTrialCreator field_trial_creator(
+      &variations_service_client, std::move(seed_store), UIStringOverrider(),
+      test_case.is_limited_entropy_synthetic_trial_applicable
+          ? &limited_entropy_synthetic_trial
+          : nullptr);
+  metrics::TestEnabledStateProvider enabled_state_provider(
+      /*consent=*/true,
+      /*enabled=*/true);
+  auto metrics_state_manager = metrics::MetricsStateManager::Create(
+      local_state(), &enabled_state_provider, std::wstring(), base::FilePath());
+  metrics_state_manager->InstantiateFieldTrialList();
+  PlatformFieldTrials platform_field_trials;
+  NiceMock<MockSafeSeedManager> safe_seed_manager(local_state());
+
+  // The client should not be registered to a group of the limited entropy
+  // synthetic trial before the test.
+  EXPECT_EQ(
+      std::string(),
+      variations_service_client.GetLimitedEntropySyntheticTrialGroupName());
+
+  EXPECT_TRUE(field_trial_creator.SetUpFieldTrials(
+      /*variation_ids=*/{},
+      /*command_line_variation_ids=*/std::string(),
+      std::vector<base::FeatureList::FeatureOverrideInfo>(),
+      std::make_unique<base::FeatureList>(), metrics_state_manager.get(),
+      &platform_field_trials, &safe_seed_manager,
+      /*add_entropy_source_to_variations_ids=*/true));
+
+  if (test_case.is_group_registration_expected) {
+    EXPECT_NE(
+        std::string(),
+        variations_service_client.GetLimitedEntropySyntheticTrialGroupName());
+  } else {
+    EXPECT_EQ(
+        std::string(),
+        variations_service_client.GetLimitedEntropySyntheticTrialGroupName());
+  }
+}
+
 // Test feature names prefixed with __ to avoid collision with real features.
 BASE_FEATURE(kDesktopFeature, "__Desktop", base::FEATURE_DISABLED_BY_DEFAULT);
 BASE_FEATURE(kPhoneFeature, "__Phone", base::FEATURE_DISABLED_BY_DEFAULT);
@@ -1445,7 +1596,7 @@
   // Set up the field trials.
   VariationsFieldTrialCreator field_trial_creator{
       &variations_service_client, CreateSeedStore(local_state()),
-      UIStringOverrider()};
+      UIStringOverrider(), /*limited_entropy_synthetic_trial=*/nullptr};
   EXPECT_TRUE(field_trial_creator.SetUpFieldTrials(
       /*variation_ids=*/{},
       /*command_line_variation_ids=*/std::string(),
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 573a4bf4..e57a948 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -351,7 +351,8 @@
               MaybeImportFirstRunSeed(client_.get(), local_state),
               /*signature_verification_enabled=*/true,
               std::make_unique<VariationsSafeSeedStoreLocalState>(local_state)),
-          ui_string_overrider) {
+          ui_string_overrider,
+          &limited_entropy_synthetic_trial_) {
   DCHECK(client_);
   DCHECK(resource_request_allowed_notifier_);
 
diff --git a/components/variations/service/variations_service_client.h b/components/variations/service/variations_service_client.h
index cc67d49..0cbf36c6 100644
--- a/components/variations/service/variations_service_client.h
+++ b/components/variations/service/variations_service_client.h
@@ -82,7 +82,8 @@
 
   // Registers the group membership of the limited entropy synthetic trial.
   // TODO(crbug.com/1508150): Remove once the trial has wrapped up.
-  void RegisterLimitedEntropySyntheticTrial(std::string_view group_name);
+  virtual void RegisterLimitedEntropySyntheticTrial(
+      std::string_view group_name);
 
  private:
   // Gets the channel of the embedder. But all variations callers should use
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index f42e4d03..09eaf14 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -749,6 +749,11 @@
     return StoreSourceResult::InternalError();
   }
 
+  DCHECK_LT(AttributionStorageSql::kCurrentVersionNumber, 86);
+  base::UmaHistogramCustomCounts("Conversions.DbVersionOnSourceStored",
+                                 kCurrentVersionNumber, /*min=*/56,
+                                 /*exclusive_max=*/86, /*buckets=*/30);
+
   if (attribution_logic == StoredSource::AttributionLogic::kTruthfully) {
     return StoreSourceResult::Success();
   }
diff --git a/content/browser/attribution_reporting/attribution_storage_unittest.cc b/content/browser/attribution_reporting/attribution_storage_unittest.cc
index dc93572..6f0bdd0 100644
--- a/content/browser/attribution_reporting/attribution_storage_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_unittest.cc
@@ -217,7 +217,10 @@
 }
 
 TEST_F(AttributionStorageTest, ImpressionStoredAndRetrieved_ValuesIdentical) {
+  base::HistogramTester histograms;
   storage()->StoreSource(SourceBuilder().Build());
+  histograms.ExpectBucketCount("Conversions.DbVersionOnSourceStored",
+                               AttributionStorageSql::kCurrentVersionNumber, 1);
   EXPECT_THAT(storage()->GetActiveSources(),
               ElementsAre(SourceBuilder().BuildStored()));
 }
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index a9efd96..80cabc5a 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -5926,13 +5926,13 @@
   device_1.state->pin = kTestPIN;
   device_1.config.pin_support = true;
   std::tie(disconnect_1, device_1.disconnect_events) =
-      device::FidoDeviceDiscovery::EventStream<bool>::New();
+      device::FidoDiscoveryBase::EventStream<bool>::New();
 
   device::test::MultipleVirtualFidoDeviceFactory::DeviceDetails device_2;
   device_2.state->pin = kTestPIN;
   device_2.config.pin_support = true;
   std::tie(disconnect_2, device_2.disconnect_events) =
-      device::FidoDeviceDiscovery::EventStream<bool>::New();
+      device::FidoDiscoveryBase::EventStream<bool>::New();
 
   int callbacks = 0;
   auto touch_callback = [&](int device_num) -> bool {
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index 84da983..c40f945 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -833,7 +833,10 @@
           /*signature_verification_enabled=*/true,
           std::make_unique<variations::VariationsSafeSeedStoreLocalState>(
               GetSharedState().local_state.get())),
-      variations::UIStringOverrider());
+      variations::UIStringOverrider(),
+      // The limited entropy synthetic trial will not be registered for this
+      // purpose.
+      /*limited_entropy_synthetic_trial=*/nullptr);
 
   variations::SafeSeedManager safe_seed_manager(
       GetSharedState().local_state.get());
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 8020694..47f19fa 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -522,7 +522,10 @@
 # been made more robust.
 # crbug.com/735483 [ mac amd release ] conformance/rendering/texture-switch-performance.html [ Failure ]
 
-crbug.com/1435021 [ amd-0x67ef angle-metal mac no-clang-coverage release ] conformance/canvas/draw-webgl-to-canvas-test.html [ Failure ]
+crbug.com/1435021 [ amd-0x67ef angle-metal mac no-clang-coverage release graphite-disabled ] conformance/canvas/draw-webgl-to-canvas-test.html [ Failure ]
+
+crbug.com/1513193 [ mac amd angle-metal graphite-enabled ] conformance/canvas/draw-webgl-to-canvas-test.html [ RetryOnFailure ]
+crbug.com/1513193 [ mac amd angle-metal graphite-enabled ] conformance/canvas/to-data-url-test.html [ RetryOnFailure ]
 
 ## Mac Intel ##
 
diff --git a/device/fido/cable/fido_cable_discovery.cc b/device/fido/cable/fido_cable_discovery.cc
index 018d1fa..e2f412f 100644
--- a/device/fido/cable/fido_cable_discovery.cc
+++ b/device/fido/cable/fido_cable_discovery.cc
@@ -153,7 +153,7 @@
     adapter_->RemoveObserver(this);
 }
 
-std::unique_ptr<FidoDeviceDiscovery::EventStream<base::span<const uint8_t, 20>>>
+std::unique_ptr<FidoDiscoveryBase::EventStream<base::span<const uint8_t, 20>>>
 FidoCableDiscovery::GetV2AdvertStream() {
   DCHECK(!advert_callback_);
 
diff --git a/device/fido/cable/fido_cable_discovery.h b/device/fido/cable/fido_cable_discovery.h
index 85b0664..f3b29ea 100644
--- a/device/fido/cable/fido_cable_discovery.h
+++ b/device/fido/cable/fido_cable_discovery.h
@@ -52,8 +52,7 @@
 
   // GetV2AdvertStream returns a stream of caBLEv2 BLE adverts. Only a single
   // stream is supported.
-  std::unique_ptr<FidoDeviceDiscovery::EventStream<
-      base::span<const uint8_t, cablev2::kAdvertSize>>>
+  std::unique_ptr<EventStream<base::span<const uint8_t, cablev2::kAdvertSize>>>
   GetV2AdvertStream();
 
   const std::map<CableEidArray, scoped_refptr<BluetoothAdvertisement>>&
diff --git a/device/fido/enclave/enclave_authenticator.cc b/device/fido/enclave/enclave_authenticator.cc
index 31635810..2c15390 100644
--- a/device/fido/enclave/enclave_authenticator.cc
+++ b/device/fido/enclave/enclave_authenticator.cc
@@ -114,11 +114,42 @@
   StartRequest();
 }
 
+void EnclaveAuthenticator::SetOauthToken(
+    absl::optional<std::string_view> token) {
+  if (token == absl::nullopt) {
+    state_ = State::kError;
+  } else {
+    websocket_client_->set_oauth_token(*token);
+  }
+
+  if (state_ == State::kWaitingForOauthToken) {
+    StartRequest();
+    return;
+  }
+
+  if (state_ == State::kInitialized) {
+    state_ = State::kOauthTokenReceived;
+  }
+}
+
 void EnclaveAuthenticator::StartRequest() {
   CHECK(!pending_get_assertion_request_ != !pending_make_credential_request_);
 
   if (state_ == State::kInitialized) {
+    state_ = State::kWaitingForOauthToken;
+    return;
+  }
+
+  if (state_ == State::kError) {
+    // This can mean we received a null OAuth token.
+    CompleteRequestWithError(CtapDeviceResponseCode::kCtap2ErrOther);
+    return;
+  }
+
+  if (state_ == State::kOauthTokenReceived ||
+      state_ == State::kWaitingForOauthToken) {
     CHECK(!handshake_);
+
     state_ = State::kWaitingForHandshakeResponse;
 
     handshake_ = std::make_unique<cablev2::HandshakeInitiator>(
diff --git a/device/fido/enclave/enclave_authenticator.h b/device/fido/enclave/enclave_authenticator.h
index 64998df2..92ed3982 100644
--- a/device/fido/enclave/enclave_authenticator.h
+++ b/device/fido/enclave/enclave_authenticator.h
@@ -6,6 +6,8 @@
 #define DEVICE_FIDO_ENCLAVE_ENCLAVE_AUTHENTICATOR_H_
 
 #include <memory>
+#include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/component_export.h"
@@ -35,8 +37,6 @@
 
 namespace enclave {
 
-// TODO(kenrb): Remove the export directive when it is no longer used by the
-// client stand-alone app.
 class COMPONENT_EXPORT(DEVICE_FIDO) EnclaveAuthenticator
     : public FidoAuthenticator {
  public:
@@ -63,9 +63,13 @@
                       MakeCredentialOptions options,
                       MakeCredentialCallback callback) override;
 
+  void SetOauthToken(absl::optional<std::string_view> token);
+
  private:
   enum class State {
     kInitialized,
+    kWaitingForOauthToken,
+    kOauthTokenReceived,
     kWaitingForHandshakeResponse,
     kConnected,
     kError,
diff --git a/device/fido/enclave/enclave_discovery.cc b/device/fido/enclave/enclave_discovery.cc
index 8d5b3d4c..9f0ba51 100644
--- a/device/fido/enclave/enclave_discovery.cc
+++ b/device/fido/enclave/enclave_discovery.cc
@@ -54,11 +54,18 @@
     std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys,
     base::RepeatingCallback<void(sync_pb::WebauthnCredentialSpecifics)>
         save_passkey_callback,
-    raw_ptr<network::mojom::NetworkContext> network_context)
+    raw_ptr<network::mojom::NetworkContext> network_context,
+    std::unique_ptr<EventStream<absl::optional<std::string_view>>>
+        oauth_token_provider)
     : FidoDiscoveryBase(FidoTransportProtocol::kInternal),
       passkeys_(std::move(passkeys)),
       save_passkey_callback_(std::move(save_passkey_callback)),
-      network_context_(network_context) {}
+      network_context_(network_context),
+      oauth_token_provider_(std::move(oauth_token_provider)) {
+  oauth_token_provider_->Connect(
+      base::BindRepeating(&EnclaveAuthenticatorDiscovery::OnOauthTokenAvailable,
+                          weak_factory_.GetWeakPtr()));
+}
 
 EnclaveAuthenticatorDiscovery::~EnclaveAuthenticatorDiscovery() = default;
 
@@ -98,4 +105,9 @@
   observer()->DiscoveryStarted(this, /*success=*/true, {authenticator_.get()});
 }
 
+void EnclaveAuthenticatorDiscovery::OnOauthTokenAvailable(
+    absl::optional<std::string_view> token) {
+  authenticator_->SetOauthToken(token);
+}
+
 }  // namespace device::enclave
diff --git a/device/fido/enclave/enclave_discovery.h b/device/fido/enclave/enclave_discovery.h
index 0cd170e9..6d9f5879 100644
--- a/device/fido/enclave/enclave_discovery.h
+++ b/device/fido/enclave/enclave_discovery.h
@@ -6,6 +6,7 @@
 #define DEVICE_FIDO_ENCLAVE_ENCLAVE_DISCOVERY_H_
 
 #include <memory>
+#include <string_view>
 
 #include "base/component_export.h"
 #include "base/memory/raw_ptr.h"
@@ -13,6 +14,7 @@
 #include "crypto/ec_private_key.h"
 #include "device/fido/fido_discovery_base.h"
 #include "services/network/public/mojom/network_context.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace sync_pb {
 class WebauthnCredentialSpecifics;
@@ -31,7 +33,9 @@
       std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys,
       base::RepeatingCallback<void(sync_pb::WebauthnCredentialSpecifics)>
           save_passkey_callback,
-      raw_ptr<network::mojom::NetworkContext> network_context);
+      raw_ptr<network::mojom::NetworkContext> network_context,
+      std::unique_ptr<EventStream<absl::optional<std::string_view>>>
+          oauth_token_provider);
   ~EnclaveAuthenticatorDiscovery() override;
 
   // FidoDiscoveryBase:
@@ -39,12 +43,15 @@
 
  private:
   void AddAuthenticator();
+  void OnOauthTokenAvailable(absl::optional<std::string_view> token);
 
   std::unique_ptr<EnclaveAuthenticator> authenticator_;
   std::vector<sync_pb::WebauthnCredentialSpecifics> passkeys_;
   base::RepeatingCallback<void(sync_pb::WebauthnCredentialSpecifics)>
       save_passkey_callback_;
   raw_ptr<network::mojom::NetworkContext> network_context_;
+  std::unique_ptr<EventStream<absl::optional<std::string_view>>>
+      oauth_token_provider_;
 
   // TODO(https://crbug.com/1459620): Temporary for the stand-in signing
   // function.
diff --git a/device/fido/enclave/enclave_websocket_client.cc b/device/fido/enclave/enclave_websocket_client.cc
index 5e2074d..26cb8ee 100644
--- a/device/fido/enclave/enclave_websocket_client.cc
+++ b/device/fido/enclave/enclave_websocket_client.cc
@@ -9,6 +9,7 @@
 #include "components/device_event_log/device_event_log.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_parsing_utils.h"
+#include "net/http/http_request_headers.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 
 namespace device::enclave {
@@ -103,6 +104,10 @@
   InternalWrite(data);
 }
 
+void EnclaveWebSocketClient::set_oauth_token(std::string_view token) {
+  oauth_token_ = token;
+}
+
 void EnclaveWebSocketClient::Connect() {
   // A disconnect handler is used so that the request can be completed in the
   // event of an unexpected disconnection from the network service.
@@ -112,15 +117,18 @@
 
   state_ = State::kConnecting;
 
+  std::vector<network::mojom::HttpHeaderPtr> additional_headers;
+  additional_headers.emplace_back(network::mojom::HttpHeader::New(
+      net::HttpRequestHeaders::kAuthorization, oauth_token_));
+
   GURL socket_url;
   GURL::Replacements replacement;
   replacement.SetPathStr(username_);
   socket_url = service_url_.ReplaceComponents(replacement);
   network_context_->CreateWebSocket(
       socket_url, {}, net::SiteForCookies(), /*has_storage_access=*/false,
-      net::IsolationInfo(),
-      /*additional_headers=*/{}, network::mojom::kBrowserProcessId,
-      url::Origin::Create(socket_url),
+      net::IsolationInfo(), std::move(additional_headers),
+      network::mojom::kBrowserProcessId, url::Origin::Create(socket_url),
       network::mojom::kWebSocketOptionBlockAllCookies,
       net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
       std::move(handshake_remote),
diff --git a/device/fido/enclave/enclave_websocket_client.h b/device/fido/enclave/enclave_websocket_client.h
index ad54de70..444a7e9 100644
--- a/device/fido/enclave/enclave_websocket_client.h
+++ b/device/fido/enclave/enclave_websocket_client.h
@@ -50,6 +50,8 @@
   // received.
   void Write(base::span<const uint8_t> data);
 
+  void set_oauth_token(std::string_view token);
+
   // WebSocketHandshakeClient:
   void OnOpeningHandshakeStarted(
       network::mojom::WebSocketHandshakeRequestPtr request) override;
@@ -93,6 +95,9 @@
   raw_ptr<network::mojom::NetworkContext> network_context_;
   OnResponseCallback on_response_;
 
+  // Token used for the Authorization header in the WebSocket connection.
+  std::string oauth_token_;
+
   // pending_read_data_ contains a partial message that is being reassembled.
   std::vector<uint8_t> pending_read_data_;
   // pending_read_data_index_ contains the number of valid bytes of
diff --git a/device/fido/fido_device_discovery.h b/device/fido/fido_device_discovery.h
index 5cb50ce..af32a8f 100644
--- a/device/fido/fido_device_discovery.h
+++ b/device/fido/fido_device_discovery.h
@@ -14,7 +14,6 @@
 #include <vector>
 
 #include "base/component_export.h"
-#include "base/functional/bind.h"
 #include "base/memory/weak_ptr.h"
 #include "device/fido/fido_discovery_base.h"
 #include "device/fido/fido_transport_protocol.h"
@@ -27,38 +26,6 @@
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceDiscovery
     : public FidoDiscoveryBase {
  public:
-  // EventStream is an unbuffered pipe that can be passed around and late-bound
-  // to the receiver.
-  template <typename T>
-  class EventStream {
-   public:
-    using Callback = base::RepeatingCallback<void(T)>;
-
-    // New returns a callback for writing events, and ownership of an
-    // |EventStream| that can be connected to in order to receive the events.
-    // The callback may outlive the |EventStream|. Any events written when
-    // either the |EventStream| has been deleted, or not yet connected, are
-    // dropped.
-    static std::pair<Callback, std::unique_ptr<EventStream<T>>> New() {
-      auto stream = std::make_unique<EventStream<T>>();
-      auto cb = base::BindRepeating(&EventStream::Transmit,
-                                    stream->weak_factory_.GetWeakPtr());
-      return std::make_pair(std::move(cb), std::move(stream));
-    }
-
-    void Connect(Callback connection) { connection_ = std::move(connection); }
-
-   private:
-    void Transmit(T t) {
-      if (connection_) {
-        connection_.Run(std::move(t));
-      }
-    }
-
-    Callback connection_;
-    base::WeakPtrFactory<EventStream<T>> weak_factory_{this};
-  };
-
   enum class State {
     kIdle,
     kStarting,
diff --git a/device/fido/fido_discovery_base.h b/device/fido/fido_discovery_base.h
index 66544cf8..2b142c1 100644
--- a/device/fido/fido_discovery_base.h
+++ b/device/fido/fido_discovery_base.h
@@ -11,7 +11,10 @@
 
 #include "base/check.h"
 #include "base/component_export.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "device/fido/fido_transport_protocol.h"
 
 namespace device {
@@ -20,6 +23,38 @@
 
 class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryBase {
  public:
+  // EventStream is an unbuffered pipe that can be passed around and late-bound
+  // to the receiver.
+  template <typename T>
+  class EventStream {
+   public:
+    using Callback = base::RepeatingCallback<void(T)>;
+
+    // New returns a callback for writing events, and ownership of an
+    // |EventStream| that can be connected to in order to receive the events.
+    // The callback may outlive the |EventStream|. Any events written when
+    // either the |EventStream| has been deleted, or not yet connected, are
+    // dropped.
+    static std::pair<Callback, std::unique_ptr<EventStream<T>>> New() {
+      auto stream = std::make_unique<EventStream<T>>();
+      auto cb = base::BindRepeating(&EventStream::Transmit,
+                                    stream->weak_factory_.GetWeakPtr());
+      return std::make_pair(std::move(cb), std::move(stream));
+    }
+
+    void Connect(Callback connection) { connection_ = std::move(connection); }
+
+   private:
+    void Transmit(T t) {
+      if (connection_) {
+        connection_.Run(std::move(t));
+      }
+    }
+
+    Callback connection_;
+    base::WeakPtrFactory<EventStream<T>> weak_factory_{this};
+  };
+
   FidoDiscoveryBase(const FidoDiscoveryBase&) = delete;
   FidoDiscoveryBase& operator=(const FidoDiscoveryBase&) = delete;
 
diff --git a/device/fido/fido_discovery_factory.cc b/device/fido/fido_discovery_factory.cc
index 9bfbdbe8..77366a66 100644
--- a/device/fido/fido_discovery_factory.cc
+++ b/device/fido/fido_discovery_factory.cc
@@ -194,6 +194,15 @@
   enclave_passkey_creation_callback_ = callback;
 }
 
+base::RepeatingCallback<void(absl::optional<std::string_view>)>
+FidoDiscoveryFactory::get_enclave_oauth_token_callback() {
+  CHECK(!oauth_token_provider_);
+  base::RepeatingCallback<void(absl::optional<std::string_view>)> ret;
+  std::tie(ret, oauth_token_provider_) =
+      FidoDiscoveryBase::EventStream<absl::optional<std::string_view>>::New();
+  return ret;
+}
+
 // static
 std::vector<std::unique_ptr<FidoDiscoveryBase>>
 FidoDiscoveryFactory::SingleDiscovery(
@@ -274,7 +283,8 @@
   discoveries.emplace_back(
       std::make_unique<enclave::EnclaveAuthenticatorDiscovery>(
           std::move(enclave_passkeys_),
-          std::move(enclave_passkey_creation_callback_), network_context_));
+          std::move(enclave_passkey_creation_callback_), network_context_,
+          std::move(oauth_token_provider_)));
 }
 #endif
 
diff --git a/device/fido/fido_discovery_factory.h b/device/fido/fido_discovery_factory.h
index ec95a51..3e043fec 100644
--- a/device/fido/fido_discovery_factory.h
+++ b/device/fido/fido_discovery_factory.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/component_export.h"
@@ -103,6 +104,11 @@
       base::RepeatingCallback<void(sync_pb::WebauthnCredentialSpecifics)>
           callback);
 
+  // Returns a callback that can be called to provide the OAuth token for
+  // connecting to the cloud enclave authenticator.
+  virtual base::RepeatingCallback<void(absl::optional<std::string_view>)>
+  get_enclave_oauth_token_callback();
+
 #if BUILDFLAG(IS_MAC)
   // Configures the Touch ID authenticator. Set to absl::nullopt to disable it.
   void set_mac_touch_id_info(
@@ -191,6 +197,9 @@
   std::vector<sync_pb::WebauthnCredentialSpecifics> enclave_passkeys_;
   base::RepeatingCallback<void(sync_pb::WebauthnCredentialSpecifics)>
       enclave_passkey_creation_callback_;
+  std::unique_ptr<
+      FidoDiscoveryBase::EventStream<absl::optional<std::string_view>>>
+      oauth_token_provider_;
 };
 
 }  // namespace device
diff --git a/device/fido/virtual_fido_device_factory.cc b/device/fido/virtual_fido_device_factory.cc
index 7e6ecc9..abf31f05 100644
--- a/device/fido/virtual_fido_device_factory.cc
+++ b/device/fido/virtual_fido_device_factory.cc
@@ -58,8 +58,8 @@
 base::RepeatingCallback<void(std::unique_ptr<cablev2::Pairing>)>
 VirtualFidoDeviceFactory::get_cable_contact_callback() {
   base::RepeatingCallback<void(std::unique_ptr<cablev2::Pairing>)> ret;
-  std::tie(ret, contact_device_stream_) = FidoDeviceDiscovery::EventStream<
-      std::unique_ptr<cablev2::Pairing>>::New();
+  std::tie(ret, contact_device_stream_) =
+      FidoDiscoveryBase::EventStream<std::unique_ptr<cablev2::Pairing>>::New();
   return ret;
 }
 
diff --git a/device/fido/virtual_fido_device_factory.h b/device/fido/virtual_fido_device_factory.h
index dff1081..11d31155 100644
--- a/device/fido/virtual_fido_device_factory.h
+++ b/device/fido/virtual_fido_device_factory.h
@@ -12,6 +12,7 @@
 #include "base/memory/scoped_refptr.h"
 #include "device/fido/cable/cable_discovery_data.h"
 #include "device/fido/fido_constants.h"
+#include "device/fido/fido_discovery_base.h"
 #include "device/fido/fido_discovery_factory.h"
 #include "device/fido/fido_transport_protocol.h"
 #include "device/fido/virtual_ctap2_device.h"
@@ -69,7 +70,7 @@
 
  private:
   std::unique_ptr<
-      FidoDeviceDiscovery::EventStream<std::unique_ptr<cablev2::Pairing>>>
+      FidoDiscoveryBase::EventStream<std::unique_ptr<cablev2::Pairing>>>
       contact_device_stream_;
   ProtocolVersion supported_protocol_ = ProtocolVersion::kU2f;
   FidoTransportProtocol transport_ =
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 4e92073..19e8a42e 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1371,6 +1371,15 @@
       <message name="IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_TO_FILES" desc="Button to start downloading a file to Files e.g. download to local storage. Files is a product name i.e. the Files app on iOS devices and the correct localized version of the product name should be used. [Length: 15em] [iOS only]" meaning="Button to start downloading a file to Files e.g. download to local storage. Files is a product name i.e. the Files app on iOS devices and the correct localized version of the product name should be used. [Length: 15em] [iOS only]">
         Files
       </message>
+      <message name="IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE" desc="Label for the download manager when the download has completed. It includes the size of the downloaded file. [iOS only]" meaning="Label for the download manager when the download has completed. It includes the size of the downloaded file. [iOS only]">
+        <ph name="FILENAME">$1<ex>image.jpeg</ex></ph> (<ph name="FILESIZE">$2<ex>4 MB</ex></ph>)
+      </message>
+      <message name="IDS_IOS_DOWNLOAD_MANAGER_SAVED_TO_DRIVE" desc="Label for the file having been downloaded to Drive. [iOS only]" meaning="Label for the file having been downloaded to Drive. [iOS only]">
+        saved in Drive for <ph name="USER_EMAIL">$1<ex>peter.parker@gmail.com</ex></ph>.
+      </message>
+      <message name="IDS_IOS_DOWNLOAD_MANAGER_SAVING_TO_DRIVE" desc="Label for the file being downloaded to Drive. [iOS only]" meaning="Label for the file being downloaded to Drive. [iOS only]">
+        saving in Drive for <ph name="USER_EMAIL">$1<ex>peter.parker@gmail.com</ex></ph>.
+      </message>
       <message name="IDS_IOS_DOWNLOAD_MANAGER_FAILED_ACCESSIBILITY_ANNOUNCEMENT" desc="The accessibility announcement read by Voice Over when the download has failed. [Length: unlimited] [iOS only]">
         Download failed
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE.png.sha1
new file mode 100644
index 0000000..73603dd
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE.png.sha1
@@ -0,0 +1 @@
+815b3d11cefbab6d982ddcb21f73ddcac3024433
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVED_TO_DRIVE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVED_TO_DRIVE.png.sha1
new file mode 100644
index 0000000..eac08d3
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVED_TO_DRIVE.png.sha1
@@ -0,0 +1 @@
+3f8c0ad5c0613c5c609eccc7eb1dfa2203fb05e1
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVING_TO_DRIVE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVING_TO_DRIVE.png.sha1
new file mode 100644
index 0000000..73603dd
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_DOWNLOAD_MANAGER_SAVING_TO_DRIVE.png.sha1
@@ -0,0 +1 @@
+815b3d11cefbab6d982ddcb21f73ddcac3024433
\ No newline at end of file
diff --git a/ios/chrome/browser/download/model/BUILD.gn b/ios/chrome/browser/download/model/BUILD.gn
index 3763a23..4219d0c9 100644
--- a/ios/chrome/browser/download/model/BUILD.gn
+++ b/ios/chrome/browser/download/model/BUILD.gn
@@ -45,6 +45,7 @@
     "//components/keyed_service/ios",
     "//components/strings",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/drive/model:drive_tab_helper",
     "//ios/chrome/browser/overlays/model",
     "//ios/chrome/browser/overlays/model/public/common/confirmation",
     "//ios/chrome/browser/overlays/model/public/common/confirmation:util",
@@ -52,6 +53,7 @@
     "//ios/chrome/browser/shared/model/browser_state",
     "//ios/chrome/browser/shared/model/utils",
     "//ios/chrome/browser/shared/public/commands",
+    "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/ui/download:features",
     "//ios/web/common",
     "//ios/web/public",
diff --git a/ios/chrome/browser/download/model/DEPS b/ios/chrome/browser/download/model/DEPS
index e45e3c1..6fba973 100644
--- a/ios/chrome/browser/download/model/DEPS
+++ b/ios/chrome/browser/download/model/DEPS
@@ -2,6 +2,7 @@
   "+ios/chrome/browser/prerender/model",
   "+ios/chrome/browser/overlays/model/public",
   "+ios/chrome/browser/optimization_guide/model",
+  "+ios/chrome/browser/drive/model",
 ]
 
 specific_include_rules = {
diff --git a/ios/chrome/browser/download/model/download_manager_tab_helper.h b/ios/chrome/browser/download/model/download_manager_tab_helper.h
index f295b74..14da962 100644
--- a/ios/chrome/browser/download/model/download_manager_tab_helper.h
+++ b/ios/chrome/browser/download/model/download_manager_tab_helper.h
@@ -43,7 +43,7 @@
   void SetDelegate(id<DownloadManagerTabHelperDelegate> delegate);
 
   // Starts the current download task and remember to save it to Drive.
-  void StartDownloadTaskAndSaveToDrive(id<SystemIdentity> selected_identity);
+  virtual void OnDownloadAddedToSaveToDrive(web::DownloadTask* task);
 
  protected:
   // Allow subclassing from DownloadManagerTabHelper for testing purposes.
@@ -63,6 +63,8 @@
   // Assigns `task` to `task_`; replaces the current download if exists;
   // instructs the delegate that download has started.
   void DidCreateDownload(std::unique_ptr<web::DownloadTask> task);
+  // Returns whether `task_` still needs to be saved to Drive.
+  bool WillDownloadTaskBeSavedToDrive() const;
 
   web::WebState* web_state_ = nullptr;
   __weak id<DownloadManagerTabHelperDelegate> delegate_ = nil;
diff --git a/ios/chrome/browser/download/model/download_manager_tab_helper.mm b/ios/chrome/browser/download/model/download_manager_tab_helper.mm
index 55c87e68..ae51761 100644
--- a/ios/chrome/browser/download/model/download_manager_tab_helper.mm
+++ b/ios/chrome/browser/download/model/download_manager_tab_helper.mm
@@ -5,9 +5,12 @@
 #import "ios/chrome/browser/download/model/download_manager_tab_helper.h"
 
 #import "base/check_op.h"
+#import "base/feature_list.h"
 #import "base/memory/ptr_util.h"
 #import "base/notreached.h"
 #import "ios/chrome/browser/download/model/download_manager_tab_helper_delegate.h"
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/web/public/download/download_task.h"
 
 DownloadManagerTabHelper::DownloadManagerTabHelper(web::WebState* web_state)
@@ -33,7 +36,8 @@
 void DownloadManagerTabHelper::Download(
     std::unique_ptr<web::DownloadTask> task) {
   // If downloads are persistent, they cannot be lost once completed.
-  if (!task_ || task_->GetState() == web::DownloadTask::State::kComplete) {
+  if (!task_ || (task_->GetState() == web::DownloadTask::State::kComplete &&
+                 !WillDownloadTaskBeSavedToDrive())) {
     // The task is the first download for this web state.
     DidCreateDownload(std::move(task));
     return;
@@ -61,9 +65,11 @@
   delegate_ = delegate;
 }
 
-void DownloadManagerTabHelper::StartDownloadTaskAndSaveToDrive(
-    id<SystemIdentity> selected_identity) {
-  // TODO(crbug.com/1495353): Start the download task through `delegate_`.
+void DownloadManagerTabHelper::OnDownloadAddedToSaveToDrive(
+    web::DownloadTask* task) {
+  DCHECK_EQ(task, task_.get());
+  [delegate_ downloadManagerTabHelper:this
+          didAddDownloadToSaveToDrive:task_.get()];
 }
 
 #pragma mark - web::WebStateObserver
@@ -130,4 +136,15 @@
   }
 }
 
+bool DownloadManagerTabHelper::WillDownloadTaskBeSavedToDrive() const {
+  if (!base::FeatureList::IsEnabled(kIOSSaveToDrive)) {
+    return false;
+  }
+  DriveTabHelper* drive_tab_helper =
+      DriveTabHelper::FromWebState(task_->GetWebState());
+  std::optional<DownloadTaskSaveToDriveData> save_to_drive_data =
+      drive_tab_helper->GetDownloadTaskSaveToDriveData();
+  return save_to_drive_data && save_to_drive_data->task == task_.get();
+}
+
 WEB_STATE_USER_DATA_KEY_IMPL(DownloadManagerTabHelper)
diff --git a/ios/chrome/browser/download/model/download_manager_tab_helper_delegate.h b/ios/chrome/browser/download/model/download_manager_tab_helper_delegate.h
index d3ef707..facc710 100644
--- a/ios/chrome/browser/download/model/download_manager_tab_helper_delegate.h
+++ b/ios/chrome/browser/download/model/download_manager_tab_helper_delegate.h
@@ -25,23 +25,29 @@
 @protocol DownloadManagerTabHelperDelegate<NSObject>
 
 // Informs the delegate that a DownloadTask was created.
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-               didCreateDownload:(nonnull web::DownloadTask*)download
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+               didCreateDownload:(web::DownloadTask*)download
                webStateIsVisible:(BOOL)webStateIsVisible;
 
 // Asks the delegate whether the new download task should replace the old
 // download task.
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-         decidePolicyForDownload:(nonnull web::DownloadTask*)download
-               completionHandler:(nonnull void (^)(NewDownloadPolicy))handler;
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+         decidePolicyForDownload:(web::DownloadTask*)download
+               completionHandler:(void (^)(NewDownloadPolicy))handler;
 
 // Informs the delegate that WebState related to this download was hidden.
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didHideDownload:(nonnull web::DownloadTask*)download;
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didHideDownload:(web::DownloadTask*)download;
 
 // Informs the delegate that WebState related to this download was shown.
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didShowDownload:(nonnull web::DownloadTask*)download;
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didShowDownload:(web::DownloadTask*)download;
+
+// Informs the delegate that `download` was added to Save to Drive and will be
+// uploaded once the download has completed. This should lead the delegate to
+// start the download task.
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+     didAddDownloadToSaveToDrive:(web::DownloadTask*)download;
 
 @end
 
diff --git a/ios/chrome/browser/drive/model/BUILD.gn b/ios/chrome/browser/drive/model/BUILD.gn
index f28fd10..808a128 100644
--- a/ios/chrome/browser/drive/model/BUILD.gn
+++ b/ios/chrome/browser/drive/model/BUILD.gn
@@ -16,6 +16,20 @@
   ]
 }
 
+source_set("drive_tab_helper") {
+  sources = [
+    "download_task_save_to_drive_data.h",
+    "drive_tab_helper.h",
+    "drive_tab_helper.mm",
+  ]
+  deps = [
+    "//base",
+    "//ios/web/public",
+    "//ios/web/public:web_state_observer",
+    "//ios/web/public/download",
+  ]
+}
+
 source_set("drive_service") {
   sources = [
     "drive_service.h",
@@ -44,11 +58,17 @@
 
 source_set("unit_tests") {
   testonly = true
-  sources = [ "drive_service_factory_unittest.mm" ]
+  sources = [
+    "drive_service_factory_unittest.mm",
+    "drive_tab_helper_unittest.mm",
+  ]
   deps = [
     ":drive_service_factory",
+    ":drive_tab_helper",
     "//base/test:test_support",
     "//ios/chrome/browser/shared/model/browser_state:test_support",
+    "//ios/chrome/browser/signin/model:fake_system_identity",
+    "//ios/web/public/test/fakes",
     "//testing/gtest",
   ]
 }
diff --git a/ios/chrome/browser/drive/model/download_task_save_to_drive_data.h b/ios/chrome/browser/drive/model/download_task_save_to_drive_data.h
new file mode 100644
index 0000000..a741abb
--- /dev/null
+++ b/ios/chrome/browser/drive/model/download_task_save_to_drive_data.h
@@ -0,0 +1,23 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_DRIVE_MODEL_DOWNLOAD_TASK_SAVE_TO_DRIVE_DATA_H_
+#define IOS_CHROME_BROWSER_DRIVE_MODEL_DOWNLOAD_TASK_SAVE_TO_DRIVE_DATA_H_
+
+@protocol SystemIdentity;
+namespace web {
+class DownloadTask;
+}
+
+// Data necessary to keep track of a DownloadTask whose resulting downloaded
+// file should be saved to Drive.
+struct DownloadTaskSaveToDriveData {
+  bool operator==(const DownloadTaskSaveToDriveData& rhs) const = default;
+  // The `DownloadTask` which will be saved to Drive.
+  raw_ptr<web::DownloadTask> task;
+  // The `identity` which should be used to save the downloaded file to Drive.
+  id<SystemIdentity> identity;
+};
+
+#endif  // IOS_CHROME_BROWSER_DRIVE_MODEL_DOWNLOAD_TASK_SAVE_TO_DRIVE_DATA_H_
diff --git a/ios/chrome/browser/drive/model/drive_tab_helper.h b/ios/chrome/browser/drive/model/drive_tab_helper.h
new file mode 100644
index 0000000..e1ddbe9
--- /dev/null
+++ b/ios/chrome/browser/drive/model/drive_tab_helper.h
@@ -0,0 +1,61 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_DRIVE_MODEL_DRIVE_TAB_HELPER_H_
+#define IOS_CHROME_BROWSER_DRIVE_MODEL_DRIVE_TAB_HELPER_H_
+
+#import "base/scoped_observation.h"
+#import "ios/chrome/browser/drive/model/download_task_save_to_drive_data.h"
+#import "ios/web/public/download/download_task.h"
+#import "ios/web/public/download/download_task_observer.h"
+#import "ios/web/public/web_state_observer.h"
+#import "ios/web/public/web_state_user_data.h"
+
+// Manages Save to Drive tab-scoped state i.e. if a `DownloadTask` is received
+// through AddDownloadToSaveToDrive(...) then this tab helper
+// - 1 - memorises the task, what `SystemIdentity` will be used to save the
+// downloaded file to Drive, etc.
+// - 2 - observes the `DownloadTask` and uploads the downloaded file using the
+// Drive service upon completion of the task.
+class DriveTabHelper : public web::WebStateUserData<DriveTabHelper>,
+                       public web::DownloadTaskObserver {
+ public:
+  DriveTabHelper(const DriveTabHelper&) = delete;
+  DriveTabHelper& operator=(const DriveTabHelper&) = delete;
+  ~DriveTabHelper() override;
+
+  // Adds a DownloadTask to Save to Drive. This download task will be observed
+  // and the downloaded file will be uploaded to Drive.
+  void AddDownloadToSaveToDrive(web::DownloadTask* task,
+                                id<SystemIdentity> identity);
+  // Returns Save to Drive data associated with the current download task.
+  std::optional<DownloadTaskSaveToDriveData> GetDownloadTaskSaveToDriveData()
+      const;
+
+ private:
+  friend class web::WebStateUserData<DriveTabHelper>;
+  explicit DriveTabHelper(web::WebState* web_state);
+
+  // web::DownloadTaskObserver overrides:
+  void OnDownloadUpdated(web::DownloadTask* task) override;
+  void OnDownloadDestroyed(web::DownloadTask* task) override;
+
+  // Resets the Save to Drive data i.e. stop observing the current task, and
+  // start observing the task in `data` if any.
+  void ResetSaveToDriveData(std::optional<DownloadTaskSaveToDriveData> data);
+
+  // Associated WebState.
+  raw_ptr<web::WebState> web_state_;
+
+  // Save to Drive data associated with the current download task.
+  std::optional<DownloadTaskSaveToDriveData> download_task_save_to_drive_data_;
+  // Scoped observation to observe the `DownloadTask`.
+  using ScopedDownloadTaskObservation =
+      base::ScopedObservation<web::DownloadTask, web::DownloadTaskObserver>;
+  ScopedDownloadTaskObservation download_task_obs_{this};
+
+  WEB_STATE_USER_DATA_KEY_DECL();
+};
+
+#endif  // IOS_CHROME_BROWSER_DRIVE_MODEL_DRIVE_TAB_HELPER_H_
diff --git a/ios/chrome/browser/drive/model/drive_tab_helper.mm b/ios/chrome/browser/drive/model/drive_tab_helper.mm
new file mode 100644
index 0000000..2c45fc6
--- /dev/null
+++ b/ios/chrome/browser/drive/model/drive_tab_helper.mm
@@ -0,0 +1,59 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
+
+DriveTabHelper::DriveTabHelper(web::WebState* web_state)
+    : web_state_(web_state) {}
+
+DriveTabHelper::~DriveTabHelper() = default;
+
+#pragma mark - Public
+
+void DriveTabHelper::AddDownloadToSaveToDrive(web::DownloadTask* task,
+                                              id<SystemIdentity> identity) {
+  ResetSaveToDriveData(DownloadTaskSaveToDriveData{
+      .task = task,
+      .identity = identity,
+  });
+}
+
+std::optional<DownloadTaskSaveToDriveData>
+DriveTabHelper::GetDownloadTaskSaveToDriveData() const {
+  return download_task_save_to_drive_data_;
+}
+
+#pragma mark - web::DownloadTaskObserver
+
+void DriveTabHelper::OnDownloadUpdated(web::DownloadTask* task) {
+  switch (task->GetState()) {
+    case web::DownloadTask::State::kComplete:
+      // TODO(crbug.com/1495354): Start uploading the file to Drive.
+    case web::DownloadTask::State::kCancelled:
+    case web::DownloadTask::State::kInProgress:
+    case web::DownloadTask::State::kFailed:
+    case web::DownloadTask::State::kFailedNotResumable:
+    case web::DownloadTask::State::kNotStarted:
+      break;
+  }
+}
+
+void DriveTabHelper::OnDownloadDestroyed(web::DownloadTask* task) {
+  ResetSaveToDriveData(std::nullopt);
+}
+
+#pragma mark - Private
+
+void DriveTabHelper::ResetSaveToDriveData(
+    std::optional<DownloadTaskSaveToDriveData> data) {
+  download_task_save_to_drive_data_ = data;
+  download_task_obs_.Reset();
+  if (data) {
+    download_task_obs_.Observe(data->task);
+  }
+}
+
+#pragma mark - web::WebStateUserData
+
+WEB_STATE_USER_DATA_KEY_IMPL(DriveTabHelper)
diff --git a/ios/chrome/browser/drive/model/drive_tab_helper_unittest.mm b/ios/chrome/browser/drive/model/drive_tab_helper_unittest.mm
new file mode 100644
index 0000000..45891f2
--- /dev/null
+++ b/ios/chrome/browser/drive/model/drive_tab_helper_unittest.mm
@@ -0,0 +1,53 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
+
+#import "ios/chrome/browser/signin/model/fake_system_identity.h"
+#import "ios/web/public/test/fakes/fake_download_task.h"
+#import "ios/web/public/test/fakes/fake_web_state.h"
+#import "testing/platform_test.h"
+
+namespace {
+
+// Constants for configuring a fake download task.
+const char kTestUrl[] = "https://chromium.test/download.txt";
+const char kTestMimeType[] = "text/html";
+
+}  // namespace
+
+// DriveTabHelper unit tests.
+class DriveTabHelperTest : public PlatformTest {
+ protected:
+  void SetUp() final {
+    PlatformTest::SetUp();
+    web_state_ = std::make_unique<web::FakeWebState>();
+    DriveTabHelper::CreateForWebState(web_state_.get());
+    download_task_ =
+        std::make_unique<web::FakeDownloadTask>(GURL(kTestUrl), kTestMimeType);
+    download_task_->SetWebState(web_state_.get());
+    helper_ = DriveTabHelper::FromWebState(web_state_.get());
+  }
+
+  std::unique_ptr<web::FakeWebState> web_state_;
+  std::unique_ptr<web::FakeDownloadTask> download_task_;
+  raw_ptr<DriveTabHelper> helper_;
+};
+
+// Tests that upon `DownloadTask` being destroyed, the `DriveTabHelper` stops
+// observing it i.e. removes itself from observers and resets its state. The
+// "removes itself from observers" bit is checked implicitly when the
+// `DownloadTask` destructor is called, since neglecting to remove itself as
+// observer would lead to a crash.
+TEST_F(DriveTabHelperTest, StopsObservingDestroyedDownloadTask) {
+  EXPECT_EQ(std::nullopt, helper_->GetDownloadTaskSaveToDriveData());
+  FakeSystemIdentity* identity = [FakeSystemIdentity fakeIdentity1];
+  helper_->AddDownloadToSaveToDrive(download_task_.get(), identity);
+  DownloadTaskSaveToDriveData expected_save_to_drive_data{
+      .task = download_task_.get(), .identity = identity};
+  EXPECT_EQ(expected_save_to_drive_data,
+            helper_->GetDownloadTaskSaveToDriveData());
+  download_task_.reset();
+  EXPECT_EQ(std::nullopt, helper_->GetDownloadTaskSaveToDriveData());
+}
diff --git a/ios/chrome/browser/tabs/model/BUILD.gn b/ios/chrome/browser/tabs/model/BUILD.gn
index 0cbeb856..18c09667 100644
--- a/ios/chrome/browser/tabs/model/BUILD.gn
+++ b/ios/chrome/browser/tabs/model/BUILD.gn
@@ -66,6 +66,7 @@
     "//ios/chrome/browser/complex_tasks/model",
     "//ios/chrome/browser/crash_report/model/breadcrumbs",
     "//ios/chrome/browser/download/model",
+    "//ios/chrome/browser/drive/model:drive_tab_helper",
     "//ios/chrome/browser/favicon",
     "//ios/chrome/browser/find_in_page/model",
     "//ios/chrome/browser/find_in_page/model:util",
diff --git a/ios/chrome/browser/tabs/model/tab_helper_util.mm b/ios/chrome/browser/tabs/model/tab_helper_util.mm
index 87b9952..1d29243 100644
--- a/ios/chrome/browser/tabs/model/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/model/tab_helper_util.mm
@@ -36,6 +36,7 @@
 #import "ios/chrome/browser/download/model/pass_kit_tab_helper.h"
 #import "ios/chrome/browser/download/model/safari_download_tab_helper.h"
 #import "ios/chrome/browser/download/model/vcard_tab_helper.h"
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
 #import "ios/chrome/browser/favicon/favicon_service_factory.h"
 #import "ios/chrome/browser/find_in_page/model/find_tab_helper.h"
 #import "ios/chrome/browser/find_in_page/model/java_script_find_tab_helper.h"
@@ -226,6 +227,11 @@
   PassKitTabHelper::CreateForWebState(web_state);
   VcardTabHelper::CreateForWebState(web_state);
 
+  // Drive tab helper.
+  if (base::FeatureList::IsEnabled(kIOSSaveToDrive)) {
+    DriveTabHelper::CreateForWebState(web_state);
+  }
+
   PageloadForegroundDurationTabHelper::CreateForWebState(web_state);
 
   LookalikeUrlTabHelper::CreateForWebState(web_state);
diff --git a/ios/chrome/browser/ui/download/BUILD.gn b/ios/chrome/browser/ui/download/BUILD.gn
index eb32fca..5455bd20 100644
--- a/ios/chrome/browser/ui/download/BUILD.gn
+++ b/ios/chrome/browser/ui/download/BUILD.gn
@@ -43,6 +43,7 @@
     "//ios/chrome/browser/download/model",
     "//ios/chrome/browser/drive/model:drive_availability",
     "//ios/chrome/browser/drive/model:drive_service_factory",
+    "//ios/chrome/browser/drive/model:drive_tab_helper",
     "//ios/chrome/browser/infobars/model",
     "//ios/chrome/browser/main/model",
     "//ios/chrome/browser/overlays/model",
@@ -57,6 +58,7 @@
     "//ios/chrome/browser/shared/ui/symbols",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/signin/model",
+    "//ios/chrome/browser/signin/model:system_identity",
     "//ios/chrome/browser/store_kit/model",
     "//ios/chrome/browser/ui/download/activities",
     "//ios/chrome/browser/ui/presenters",
diff --git a/ios/chrome/browser/ui/download/download_manager_consumer.h b/ios/chrome/browser/ui/download/download_manager_consumer.h
index 344d8ba..f676193 100644
--- a/ios/chrome/browser/ui/download/download_manager_consumer.h
+++ b/ios/chrome/browser/ui/download/download_manager_consumer.h
@@ -9,6 +9,16 @@
 
 #import "ios/chrome/browser/ui/download/download_manager_state.h"
 
+// Possible destinations for the downloaded file.
+enum class DownloadFileDestination {
+  // The file is downloaded to a temporary location, and then moved to the
+  // Downloads folder on local storage.
+  kFiles,
+  // The file is downloaded to a temporary location, then uploaded to Drive. The
+  // local copy is removed.
+  kDrive,
+};
+
 // Consumer for the download manager mediator.
 @protocol DownloadManagerConsumer <NSObject>
 
@@ -37,6 +47,13 @@
 // task has also not started.
 - (void)setDownloadToDriveButtonVisible:(BOOL)visible;
 
+// Sets the destination for the downloaded file e.g. Files or Drive.
+- (void)setDownloadFileDestination:(DownloadFileDestination)destination;
+
+// If the downloaded file is being saved to Drive, sets the associated user
+// email.
+- (void)setSaveToDriveUserEmail:(NSString*)userEmail;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_DOWNLOAD_DOWNLOAD_MANAGER_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/download/download_manager_coordinator.mm b/ios/chrome/browser/ui/download/download_manager_coordinator.mm
index 8d7fee22..bfb770174 100644
--- a/ios/chrome/browser/ui/download/download_manager_coordinator.mm
+++ b/ios/chrome/browser/ui/download/download_manager_coordinator.mm
@@ -152,8 +152,8 @@
 
 #pragma mark - DownloadManagerTabHelperDelegate
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-               didCreateDownload:(nonnull web::DownloadTask*)download
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+               didCreateDownload:(web::DownloadTask*)download
                webStateIsVisible:(BOOL)webStateIsVisible {
   base::UmaHistogramEnumeration("Download.IOSDownloadFileUI",
                                 DownloadFileUI::DownloadFileStarted,
@@ -181,9 +181,9 @@
   }
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-         decidePolicyForDownload:(nonnull web::DownloadTask*)download
-               completionHandler:(nonnull void (^)(NewDownloadPolicy))handler {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+         decidePolicyForDownload:(web::DownloadTask*)download
+               completionHandler:(void (^)(NewDownloadPolicy))handler {
   std::unique_ptr<OverlayRequest> request =
       OverlayRequest::CreateWithConfig<ConfirmDownloadReplacingRequest>();
 
@@ -211,16 +211,16 @@
       ->AddRequest(std::move(request));
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didHideDownload:(nonnull web::DownloadTask*)download {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didHideDownload:(web::DownloadTask*)download {
   DCHECK_EQ(_downloadTask, download);
   self.animatesPresentation = NO;
   [self stop];
   self.animatesPresentation = YES;
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didShowDownload:(nonnull web::DownloadTask*)download {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didShowDownload:(web::DownloadTask*)download {
   DCHECK_NE(_downloadTask, download);
   _downloadTask = download;
   self.animatesPresentation = NO;
@@ -228,6 +228,14 @@
   self.animatesPresentation = YES;
 }
 
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+     didAddDownloadToSaveToDrive:(web::DownloadTask*)download {
+  DCHECK_EQ(_downloadTask, download);
+  base::RecordAction(
+      base::UserMetricsAction("IOSDownloadStartDownloadToDrive"));
+  _mediator.StartDownloading();
+}
+
 #pragma mark - ContainedPresenterDelegate
 
 - (void)containedPresenterDidPresent:(id<ContainedPresenter>)presenter {
@@ -241,7 +249,7 @@
 #pragma mark - DownloadManagerViewControllerDelegate
 
 - (void)downloadManagerViewControllerDidClose:(UIViewController*)controller {
-  if (_downloadTask->GetState() != web::DownloadTask::State::kInProgress) {
+  if (_mediator.GetDownloadManagerState() != kDownloadManagerStateInProgress) {
     base::UmaHistogramEnumeration("Download.IOSDownloadFileResult",
                                   DownloadFileResult::NotStarted,
                                   DownloadFileResult::Count);
@@ -286,7 +294,7 @@
     base::RecordAction(base::UserMetricsAction("IOSDownloadStartDownload"));
     _unopenedDownloads.Add(_downloadTask);
   }
-  _mediator.StartDowloading();
+  _mediator.StartDownloading();
 }
 
 - (void)downloadManagerViewControllerDidStartDownloadToDrive:
diff --git a/ios/chrome/browser/ui/download/download_manager_mediator.h b/ios/chrome/browser/ui/download/download_manager_mediator.h
index aa059b6d..b2964d6 100644
--- a/ios/chrome/browser/ui/download/download_manager_mediator.h
+++ b/ios/chrome/browser/ui/download/download_manager_mediator.h
@@ -55,7 +55,10 @@
   base::FilePath GetDownloadPath();
 
   // Asynchronously starts download operation.
-  void StartDowloading();
+  void StartDownloading();
+
+  // Converts web::DownloadTask::State to DownloadManagerState.
+  DownloadManagerState GetDownloadManagerState() const;
 
  private:
   // Updates consumer from web::DownloadTask.
@@ -68,9 +71,6 @@
   // Checks if the move has been completed.
   void MoveComplete(bool move_completed);
 
-  // Converts web::DownloadTask::State to DownloadManagerState.
-  DownloadManagerState GetDownloadManagerState() const;
-
   // Converts DownloadTask progress [0;100] to float progress [0.0f;1.0f].
   float GetDownloadManagerProgress() const;
 
diff --git a/ios/chrome/browser/ui/download/download_manager_mediator.mm b/ios/chrome/browser/ui/download/download_manager_mediator.mm
index a08f3444..bd9effc 100644
--- a/ios/chrome/browser/ui/download/download_manager_mediator.mm
+++ b/ios/chrome/browser/ui/download/download_manager_mediator.mm
@@ -15,17 +15,78 @@
 #import "ios/chrome/browser/download/model/download_directory_util.h"
 #import "ios/chrome/browser/download/model/external_app_util.h"
 #import "ios/chrome/browser/drive/model/drive_availability.h"
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
+#import "ios/chrome/browser/signin/model/system_identity.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/web/public/download/download_task.h"
 #import "net/base/net_errors.h"
 #import "ui/base/l10n/l10n_util.h"
 
+namespace {
+
+// Returns the Save to Drive data associated with `task` if any.
+std::optional<DownloadTaskSaveToDriveData> GetDownloadTaskSaveToDriveData(
+    web::DownloadTask* task) {
+  CHECK_NE(task, nullptr);
+  if (!base::FeatureList::IsEnabled(kIOSSaveToDrive)) {
+    return std::nullopt;
+  }
+  DriveTabHelper* drive_tab_helper =
+      DriveTabHelper::FromWebState(task->GetWebState());
+  return drive_tab_helper->GetDownloadTaskSaveToDriveData();
+}
+
+// Returns the downloaded file destination for `task`. `task` cannot be nullptr.
+DownloadFileDestination GetDownloadTaskFileDestination(
+    web::DownloadTask* task) {
+  CHECK_NE(task, nullptr);
+  if (GetDownloadTaskSaveToDriveData(task)) {
+    return DownloadFileDestination::kDrive;
+  }
+
+  return DownloadFileDestination::kFiles;
+}
+
+// If the `task` will be saved to Drive, returns the Save to Drive user email
+// address associated with `task`.
+NSString* GetDownloadTaskSaveToDriveUserEmail(web::DownloadTask* task) {
+  CHECK_NE(task, nullptr);
+  std::optional<DownloadTaskSaveToDriveData> task_save_to_drive_data =
+      GetDownloadTaskSaveToDriveData(task);
+  CHECK(task_save_to_drive_data);
+  return task_save_to_drive_data->identity.userEmail;
+}
+
+// Returns whether `task` still needs to be saved to Drive.
+bool WillDownloadTaskBeSavedToDrive(web::DownloadTask* task) {
+  CHECK_NE(task, nullptr);
+  std::optional<DownloadTaskSaveToDriveData> task_save_to_drive_data =
+      GetDownloadTaskSaveToDriveData(task);
+  if (!task_save_to_drive_data) {
+    return false;
+  }
+
+  // TODO(crbug.com/1495354): Return false if the task has been saved to Drive.
+  return true;
+}
+
+// Returns whether `task` still needs to be saved to Drive.
+float GetDownloadTaskSaveToDriveProgress(web::DownloadTask* task) {
+  CHECK_NE(task, nullptr);
+  // TODO(crbug.com/1495354): Return the actual upload progress.
+  return 0.0f;
+}
+
+}  // namespace
+
 DownloadManagerMediator::DownloadManagerMediator() : weak_ptr_factory_(this) {}
 DownloadManagerMediator::~DownloadManagerMediator() {
   SetDownloadTask(nullptr);
 }
 
+#pragma mark - Public
+
 void DownloadManagerMediator::SetIsIncognito(bool is_incognito) {
   is_incognito_ = is_incognito;
 }
@@ -61,7 +122,7 @@
   return download_path_;
 }
 
-void DownloadManagerMediator::StartDowloading() {
+void DownloadManagerMediator::StartDownloading() {
   base::FilePath download_dir;
   if (!GetTempDownloadsDirectory(&download_dir)) {
     [consumer_ setState:kDownloadManagerStateFailed];
@@ -76,26 +137,44 @@
   task_->Start(download_dir.Append(task_->GenerateFileName()));
 }
 
-void DownloadManagerMediator::OnDownloadUpdated(web::DownloadTask* task) {
-  UpdateConsumer();
+DownloadManagerState DownloadManagerMediator::GetDownloadManagerState() const {
+  // Returns the `DownloadManagerState`, depending on the state of `task_` and
+  // the state of the upload to Save to Drive, if that is the destination of the
+  // downloaded file.
+  switch (task_->GetState()) {
+    case web::DownloadTask::State::kNotStarted:
+      return kDownloadManagerStateNotStarted;
+    case web::DownloadTask::State::kInProgress:
+      return kDownloadManagerStateInProgress;
+    case web::DownloadTask::State::kComplete:
+      if (WillDownloadTaskBeSavedToDrive(task_)) {
+        return kDownloadManagerStateInProgress;
+      } else {
+        return kDownloadManagerStateSucceeded;
+      }
+    case web::DownloadTask::State::kFailed:
+      return kDownloadManagerStateFailed;
+    case web::DownloadTask::State::kFailedNotResumable:
+      return kDownloadManagerStateFailedNotResumable;
+    case web::DownloadTask::State::kCancelled:
+      // Download Manager should dismiss the UI after download cancellation.
+      return kDownloadManagerStateNotStarted;
+  }
 }
 
-void DownloadManagerMediator::OnDownloadDestroyed(web::DownloadTask* task) {
-  SetDownloadTask(nullptr);
-}
+#pragma mark - Private
 
 void DownloadManagerMediator::UpdateConsumer() {
   DownloadManagerState state = GetDownloadManagerState();
 
-  if (base::FeatureList::IsEnabled(kIOSSaveToDrive) &&
-      [consumer_
-          respondsToSelector:@selector(setDownloadToDriveButtonVisible:)]) {
+  if (base::FeatureList::IsEnabled(kIOSSaveToDrive)) {
     bool is_save_to_drive_available = drive::IsSaveToDriveAvailable(
         is_incognito_, identity_manager_, drive_service_);
     [consumer_ setDownloadToDriveButtonVisible:is_save_to_drive_available];
   }
 
-  if (state == kDownloadManagerStateSucceeded) {
+  if (state == kDownloadManagerStateSucceeded &&
+      !WillDownloadTaskBeSavedToDrive(task_)) {
     base::FilePath user_download_path;
     GetDownloadsDirectory(&user_download_path);
     download_path_ = user_download_path.Append(task_->GenerateFileName());
@@ -112,12 +191,20 @@
   }
 
   if (!base::FeatureList::IsEnabled(kIOSSaveToDrive) &&
-      state == kDownloadManagerStateSucceeded && !IsGoogleDriveAppInstalled() &&
-      [consumer_ respondsToSelector:@selector(setInstallDriveButtonVisible:
-                                                                  animated:)]) {
+      state == kDownloadManagerStateSucceeded && !IsGoogleDriveAppInstalled()) {
     [consumer_ setInstallDriveButtonVisible:YES animated:YES];
   }
 
+  if (base::FeatureList::IsEnabled(kIOSSaveToDrive)) {
+    [consumer_
+        setDownloadFileDestination:GetDownloadTaskFileDestination(task_)];
+
+    if (WillDownloadTaskBeSavedToDrive(task_)) {
+      [consumer_
+          setSaveToDriveUserEmail:GetDownloadTaskSaveToDriveUserEmail(task_)];
+    }
+  }
+
   [consumer_ setState:state];
   [consumer_ setCountOfBytesReceived:task_->GetReceivedBytes()];
   [consumer_ setCountOfBytesExpectedToReceive:task_->GetTotalBytes()];
@@ -152,24 +239,6 @@
   DCHECK(move_completed);
 }
 
-DownloadManagerState DownloadManagerMediator::GetDownloadManagerState() const {
-  switch (task_->GetState()) {
-    case web::DownloadTask::State::kNotStarted:
-      return kDownloadManagerStateNotStarted;
-    case web::DownloadTask::State::kInProgress:
-      return kDownloadManagerStateInProgress;
-    case web::DownloadTask::State::kComplete:
-      return kDownloadManagerStateSucceeded;
-    case web::DownloadTask::State::kFailed:
-      return kDownloadManagerStateFailed;
-    case web::DownloadTask::State::kFailedNotResumable:
-      return kDownloadManagerStateFailedNotResumable;
-    case web::DownloadTask::State::kCancelled:
-      // Download Manager should dismiss the UI after download cancellation.
-      return kDownloadManagerStateNotStarted;
-  }
-}
-
 int DownloadManagerMediator::GetDownloadManagerA11yAnnouncement() const {
   switch (task_->GetState()) {
     case web::DownloadTask::State::kNotStarted:
@@ -189,5 +258,24 @@
 float DownloadManagerMediator::GetDownloadManagerProgress() const {
   if (task_->GetPercentComplete() == -1)
     return 0.0f;
-  return static_cast<float>(task_->GetPercentComplete()) / 100.0f;
+  float download_progress =
+      static_cast<float>(task_->GetPercentComplete()) / 100.0f;
+  if (!WillDownloadTaskBeSavedToDrive(task_)) {
+    return download_progress;
+  }
+  float save_to_drive_progress = GetDownloadTaskSaveToDriveProgress(task_);
+  // If the downloaded file needs to be uploaded to Drive, then the overall
+  // progress is 50% once the download is complete, and then reaches 100% when
+  // the upload is complete.
+  return download_progress / 2.0 + save_to_drive_progress / 2.0;
+}
+
+#pragma mark - web::DownloadTaskObserver overrides
+
+void DownloadManagerMediator::OnDownloadUpdated(web::DownloadTask* task) {
+  UpdateConsumer();
+}
+
+void DownloadManagerMediator::OnDownloadDestroyed(web::DownloadTask* task) {
+  SetDownloadTask(nullptr);
 }
diff --git a/ios/chrome/browser/ui/download/download_manager_mediator_unittest.mm b/ios/chrome/browser/ui/download/download_manager_mediator_unittest.mm
index 3288d30..828c878 100644
--- a/ios/chrome/browser/ui/download/download_manager_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/download/download_manager_mediator_unittest.mm
@@ -61,7 +61,7 @@
   auto task =
       std::make_unique<web::FakeDownloadTask>(GURL(kTestUrl), kTestMimeType);
   mediator_.SetDownloadTask(task.get());
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
   task.reset();
 }
 
@@ -72,7 +72,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
@@ -103,7 +103,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
@@ -136,7 +136,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
@@ -183,7 +183,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
@@ -207,7 +207,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
@@ -240,7 +240,7 @@
   task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
   mediator_.SetDownloadTask(task());
   mediator_.SetConsumer(consumer_);
-  mediator_.StartDowloading();
+  mediator_.StartDownloading();
 
   // Starting download is async for task and sync for consumer.
   EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
diff --git a/ios/chrome/browser/ui/download/download_manager_view_controller.mm b/ios/chrome/browser/ui/download/download_manager_view_controller.mm
index d648d3da..b6be9b50 100644
--- a/ios/chrome/browser/ui/download/download_manager_view_controller.mm
+++ b/ios/chrome/browser/ui/download/download_manager_view_controller.mm
@@ -31,13 +31,6 @@
     @"google_drive_app_with_background";
 #endif
 
-// Possible icons for Download buttons.
-enum class DownloadDestinationIcon {
-  kNoIcon,
-  kFilesIcon,
-  kDriveIcon,
-};
-
 // `self.view` constants.
 constexpr CGFloat kWidthConstraintRegularMultiplier = 0.6;
 constexpr CGFloat kWidthConstraintCompactMultiplier = 1.0;
@@ -73,18 +66,16 @@
 
 // Returns the appropriate image for a destination icon, with or without
 // background.
-UIImage* GetDownloadDestinationIconImage(DownloadDestinationIcon icon,
+UIImage* GetDownloadFileDestinationImage(DownloadFileDestination destination,
                                          bool with_background) {
   NSString* image_name = nil;
 #if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
-  switch (icon) {
-    case DownloadDestinationIcon::kNoIcon:
-      break;
-    case DownloadDestinationIcon::kFilesIcon:
+  switch (destination) {
+    case DownloadFileDestination::kFiles:
       image_name =
           with_background ? kFilesAppWithBackgroundImage : kFilesAppImage;
       break;
-    case DownloadDestinationIcon::kDriveIcon:
+    case DownloadFileDestination::kDrive:
       image_name =
           with_background ? kDriveAppWithBackgroundImage : kDriveAppImage;
       break;
@@ -97,8 +88,9 @@
 // Creates a button configuration for a download button.
 UIButtonConfiguration* CreateDownloadButtonConfiguration(
     NSString* title,
-    DownloadDestinationIcon destination_icon,
-    bool use_image_with_background) {
+    DownloadFileDestination destination,
+    bool use_image,
+    bool use_image_background) {
   UIButtonConfiguration* conf = [UIButtonConfiguration grayButtonConfiguration];
   conf.contentInsets = NSDirectionalEdgeInsetsMake(
       kDownloadButtonVerticalInset, kDownloadButtonHorizontalInset,
@@ -106,8 +98,10 @@
   conf.imagePlacement = NSDirectionalRectEdgeTop;
   conf.imagePadding = kDownloadButtonImagePadding;
 #if BUILDFLAG(IOS_USE_BRANDED_SYMBOLS)
-  conf.image = GetDownloadDestinationIconImage(destination_icon,
-                                               use_image_with_background);
+  if (use_image) {
+    conf.image =
+        GetDownloadFileDestinationImage(destination, use_image_background);
+  }
 #endif
   if (title) {
     NSMutableParagraphStyle* centered_style =
@@ -139,6 +133,8 @@
   float _progress;
   DownloadManagerState _state;
   BOOL _downloadToDriveButtonVisible;
+  DownloadFileDestination _downloadFileDestination;
+  NSString* _saveToDriveUserEmail;
   BOOL _addedConstraints;  // YES if NSLayoutConstraits were added.
 
   // UI elements.
@@ -351,6 +347,20 @@
   }
 }
 
+- (void)setDownloadFileDestination:(DownloadFileDestination)destination {
+  if (_downloadFileDestination != destination) {
+    _downloadFileDestination = destination;
+    [self updateViews];
+  }
+}
+
+- (void)setSaveToDriveUserEmail:(NSString*)userEmail {
+  if (![userEmail isEqualToString:_saveToDriveUserEmail]) {
+    _saveToDriveUserEmail = userEmail;
+    [self updateViews];
+  }
+}
+
 #pragma mark - DownloadManagerViewControllerProtocol
 
 - (UIView*)openInSourceView {
@@ -426,8 +436,8 @@
 - (UIButton*)downloadToFilesButton {
   if (!_downloadToFilesButton) {
     UIButtonConfiguration* downloadToFilesButtonConf =
-        CreateDownloadButtonConfiguration(
-            nil, DownloadDestinationIcon::kFilesIcon, true);
+        CreateDownloadButtonConfiguration(nil, DownloadFileDestination::kFiles,
+                                          false, false);
     __weak __typeof(self) weakSelf = self;
     UIAction* downloadToFilesAction =
         [UIAction actionWithHandler:^(UIAction* action) {
@@ -454,8 +464,8 @@
 - (UIButton*)downloadToDriveButton {
   if (!_downloadToDriveButton) {
     UIButtonConfiguration* downloadToDriveButtonConf =
-        CreateDownloadButtonConfiguration(
-            nil, DownloadDestinationIcon::kDriveIcon, true);
+        CreateDownloadButtonConfiguration(nil, DownloadFileDestination::kDrive,
+                                          false, false);
     __weak __typeof(self) weakSelf = self;
     UIAction* downloadToDriveAction =
         [UIAction actionWithHandler:^(UIAction* action) {
@@ -687,43 +697,92 @@
       _downloadToDriveButtonVisible
           ? l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_TO_FILES)
           : l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD),
-      _downloadToDriveButtonVisible ? DownloadDestinationIcon::kFilesIcon
-                                    : DownloadDestinationIcon::kNoIcon,
+      DownloadFileDestination::kFiles, _downloadToDriveButtonVisible,
       self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);
   self.downloadToDriveButton.configuration = CreateDownloadButtonConfiguration(
       l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_TO_DRIVE),
-      DownloadDestinationIcon::kDriveIcon,
+      DownloadFileDestination::kDrive, true,
       self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark);
 }
 
 // Sets up views for the state `kDownloadManagerStateInProgress`.
 - (void)updateViewsForStateInProgress {
-  self.leadingIcon.image = GetDownloadDestinationIconImage(
-      DownloadDestinationIcon::kFilesIcon, true);
-  std::u16string size =
-      base::SysNSStringToUTF16(GetSizeString(_countOfBytesReceived));
-  self.statusLabel.text = l10n_util::GetNSStringF(
-      IDS_IOS_DOWNLOAD_MANAGER_DOWNLOADING_ELIPSIS, size);
+  self.leadingIcon.image =
+      GetDownloadFileDestinationImage(_downloadFileDestination, true);
 
-  self.detailLabel.text = _fileName;
-  self.detailLabel.numberOfLines = 1;
+  switch (_downloadFileDestination) {
+      // File is being downloaded to local Downloads folder.
+    case DownloadFileDestination::kFiles: {
+      std::u16string size =
+          base::SysNSStringToUTF16(GetSizeString(_countOfBytesReceived));
+      self.statusLabel.text = l10n_util::GetNSStringF(
+          IDS_IOS_DOWNLOAD_MANAGER_DOWNLOADING_ELIPSIS, size);
+
+      self.detailLabel.text = _fileName;
+      self.detailLabel.numberOfLines = 1;
+      break;
+    }
+    // File is being downloaded, then uploaded to Drive.
+    case DownloadFileDestination::kDrive: {
+      if (_countOfBytesExpectedToReceive == -1) {
+        self.statusLabel.text = _fileName;
+        self.statusLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+      } else {
+        NSString* size = GetSizeString(_countOfBytesExpectedToReceive);
+        self.statusLabel.text =
+            l10n_util::GetNSStringF(IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE,
+                                    base::SysNSStringToUTF16(_fileName),
+                                    base::SysNSStringToUTF16(size));
+        self.statusLabel.numberOfLines = 0;
+      }
+      self.detailLabel.text = l10n_util::GetNSStringF(
+          IDS_IOS_DOWNLOAD_MANAGER_SAVING_TO_DRIVE,
+          base::SysNSStringToUTF16(_saveToDriveUserEmail));
+      self.detailLabel.numberOfLines = 0;
+      break;
+    }
+  }
+
   self.progressView.progress = _progress;
 }
 
 // Sets up views for the state `kDownloadManagerStateSucceeded`.
 - (void)updateViewsForStateSucceeded {
-  self.leadingIcon.image = GetDownloadDestinationIconImage(
-      DownloadDestinationIcon::kFilesIcon, true);
-  self.statusLabel.text =
-      l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_COMPLETE);
-  self.detailLabel.text = _fileName;
-  self.detailLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+  self.leadingIcon.image =
+      GetDownloadFileDestinationImage(_downloadFileDestination, true);
+  switch (_downloadFileDestination) {
+    // File was downloaded to local Downloads folder.
+    case DownloadFileDestination::kFiles:
+      self.statusLabel.text =
+          l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_DOWNLOAD_COMPLETE);
+      self.detailLabel.text = _fileName;
+      self.detailLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+      break;
+    // File was downloaded, then uploaded to Drive.
+    case DownloadFileDestination::kDrive:
+      if (_countOfBytesExpectedToReceive == -1) {
+        self.statusLabel.text = _fileName;
+        self.statusLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
+      } else {
+        NSString* size = GetSizeString(_countOfBytesExpectedToReceive);
+        self.statusLabel.text =
+            l10n_util::GetNSStringF(IDS_IOS_DOWNLOAD_MANAGER_FILENAME_WITH_SIZE,
+                                    base::SysNSStringToUTF16(_fileName),
+                                    base::SysNSStringToUTF16(size));
+        self.statusLabel.numberOfLines = 0;
+      }
+      self.detailLabel.text = l10n_util::GetNSStringF(
+          IDS_IOS_DOWNLOAD_MANAGER_SAVED_TO_DRIVE,
+          base::SysNSStringToUTF16(_saveToDriveUserEmail));
+      self.detailLabel.numberOfLines = 0;
+      break;
+  }
 }
 
 // Sets up views for the state `kDownloadManagerStateFailed`.
 - (void)updateViewsForStateFailed {
-  self.leadingIcon.image = GetDownloadDestinationIconImage(
-      DownloadDestinationIcon::kFilesIcon, true);
+  self.leadingIcon.image =
+      GetDownloadFileDestinationImage(_downloadFileDestination, true);
   self.statusLabel.text =
       l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_COULDNT_DOWNLOAD);
   self.detailLabel.text = _fileName;
@@ -732,8 +791,8 @@
 
 // Sets up views for the state `kDownloadManagerStateFailedNotResumable`.
 - (void)updateViewsForStateFailedNotResumable {
-  self.leadingIcon.image = GetDownloadDestinationIconImage(
-      DownloadDestinationIcon::kFilesIcon, true);
+  self.leadingIcon.image =
+      GetDownloadFileDestinationImage(_downloadFileDestination, true);
   self.statusLabel.text =
       l10n_util::GetNSString(IDS_IOS_DOWNLOAD_MANAGER_CANNOT_BE_RETRIED);
   self.detailLabel.text = _fileName;
diff --git a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
index 3975a43d..f6a1c86 100644
--- a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
+++ b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <unordered_map>
 
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_multi_source_observation.h"
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/omnibox/browser/omnibox_client.h"
@@ -24,8 +25,8 @@
 class WebState;
 }  // namespace web
 
-class ChromeOmniboxClientIOS : public OmniboxClient,
-                               public web::WebStateObserver {
+class ChromeOmniboxClientIOS final : public OmniboxClient,
+                                     public web::WebStateObserver {
  public:
   ChromeOmniboxClientIOS(WebLocationBar* location_bar,
                          ChromeBrowserState* browser_state,
@@ -85,6 +86,7 @@
       const AutocompleteMatch& alternative_nav_match,
       IDNA2008DeviationCharacter deviation_char_in_hostname) override;
   LocationBarModel* GetLocationBarModel() override;
+  base::WeakPtr<OmniboxClient> AsWeakPtr() override;
 
   // web::WebStateObserver.
   void DidFinishNavigation(web::WebState* web_state,
@@ -109,6 +111,8 @@
   // Automatically remove this observer from its host when destroyed.
   base::ScopedMultiSourceObservation<web::WebState, web::WebStateObserver>
       scoped_observations_{this};
+
+  base::WeakPtrFactory<ChromeOmniboxClientIOS> weak_factory_{this};
 };
 
 #endif  // IOS_CHROME_BROWSER_UI_OMNIBOX_CHROME_OMNIBOX_CLIENT_IOS_H_
diff --git a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
index c00527e0..a1ae0b4 100644
--- a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
+++ b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
@@ -274,6 +274,10 @@
   return location_bar_->GetLocationBarModel();
 }
 
+base::WeakPtr<OmniboxClient> ChromeOmniboxClientIOS::AsWeakPtr() {
+  return weak_factory_.GetWeakPtr();
+}
+
 void ChromeOmniboxClientIOS::DidFinishNavigation(
     web::WebState* web_state,
     web::NavigationContext* navigation_context) {
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
index ffd29b44..95973673 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_controller.mm
@@ -208,6 +208,7 @@
   if (_isTextfieldEditing == owns) {
     return;
   }
+#if !defined(__IPHONE_16_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_0
   if (owns) {
     [[NSNotificationCenter defaultCenter]
         addObserver:self
@@ -220,6 +221,7 @@
                   name:UIMenuControllerWillShowMenuNotification
                 object:nil];
   }
+#endif
   _isTextfieldEditing = owns;
 }
 
@@ -583,6 +585,7 @@
       }));
 }
 
+#if !defined(__IPHONE_16_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_0
 - (void)menuControllerWillShow:(NSNotification*)notification {
   if (self.showingEditMenu || !self.isTextfieldEditing ||
       !self.textField.window.isKeyWindow) {
@@ -602,6 +605,7 @@
 
   self.showingEditMenu = NO;
 }
+#endif
 
 - (void)pasteboardDidChange:(NSNotification*)notification {
   [self updateCachedClipboardState];
diff --git a/ios/chrome/browser/ui/save_to_drive/BUILD.gn b/ios/chrome/browser/ui/save_to_drive/BUILD.gn
index efd6e89..7394329 100644
--- a/ios/chrome/browser/ui/save_to_drive/BUILD.gn
+++ b/ios/chrome/browser/ui/save_to_drive/BUILD.gn
@@ -14,6 +14,7 @@
     "//base",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/download/model",
+    "//ios/chrome/browser/drive/model:drive_tab_helper",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/model/browser",
     "//ios/chrome/browser/shared/public/commands",
@@ -52,6 +53,8 @@
     "//base/test:test_support",
     "//components/signin/public/identity_manager:test_support",
     "//ios/chrome/app/strings:ios_strings_grit",
+    "//ios/chrome/browser/download/model",
+    "//ios/chrome/browser/drive/model:drive_tab_helper",
     "//ios/chrome/browser/shared/model/browser/test:test_support",
     "//ios/chrome/browser/shared/model/browser_state:test_support",
     "//ios/chrome/browser/shared/model/web_state_list",
diff --git a/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.mm b/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.mm
index 78e1baa..04fd327b 100644
--- a/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.mm
+++ b/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.mm
@@ -5,6 +5,7 @@
 #import "ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.h"
 
 #import "ios/chrome/browser/download/model/download_manager_tab_helper.h"
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
 #import "ios/chrome/browser/shared/public/commands/save_to_drive_commands.h"
 #import "ios/chrome/browser/signin/model/system_identity.h"
 #import "ios/web/public/download/download_task.h"
@@ -59,9 +60,11 @@
   if (!_downloadTask || !_webState) {
     return;
   }
+  DriveTabHelper* driveTabHelper = DriveTabHelper::FromWebState(_webState);
   DownloadManagerTabHelper* downloadManagerTabHelper =
       DownloadManagerTabHelper::FromWebState(_webState);
-  downloadManagerTabHelper->StartDownloadTaskAndSaveToDrive(identity);
+  driveTabHelper->AddDownloadToSaveToDrive(_downloadTask, identity);
+  downloadManagerTabHelper->OnDownloadAddedToSaveToDrive(_downloadTask);
 }
 
 #pragma mark - CRWDownloadTaskObserver
diff --git a/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator_unittest.mm b/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator_unittest.mm
index 29ac2c0f..25d64a8 100644
--- a/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator_unittest.mm
@@ -4,7 +4,10 @@
 
 #import "ios/chrome/browser/ui/save_to_drive/save_to_drive_mediator.h"
 
+#import "ios/chrome/browser/download/model/download_manager_tab_helper.h"
+#import "ios/chrome/browser/drive/model/drive_tab_helper.h"
 #import "ios/chrome/browser/shared/public/commands/save_to_drive_commands.h"
+#import "ios/chrome/browser/signin/model/fake_system_identity.h"
 #import "ios/web/public/test/fakes/fake_download_task.h"
 #import "ios/web/public/test/fakes/fake_web_state.h"
 #import "testing/platform_test.h"
@@ -17,6 +20,27 @@
 
 }  // namespace
 
+#pragma mark - FakeDownloadManagerTabHelper
+
+// Fake `DownloadManagerTabHelper` to override `OnDownloadAddedToSaveToDrive()`.
+class FakeDownloadManagerTabHelper final : public DownloadManagerTabHelper {
+ public:
+  explicit FakeDownloadManagerTabHelper(web::WebState* web_state)
+      : DownloadManagerTabHelper(web_state) {}
+
+  static void CreateForWebState(web::WebState* web_state) {
+    web_state->SetUserData(
+        UserDataKey(),
+        std::make_unique<FakeDownloadManagerTabHelper>(web_state));
+  }
+
+  void OnDownloadAddedToSaveToDrive(web::DownloadTask* task) override {
+    download_task_added_to_save_to_drive_ = task;
+  }
+
+  raw_ptr<web::DownloadTask> download_task_added_to_save_to_drive_ = nullptr;
+};
+
 #pragma mark - FakeSaveToDriveCommandsHandler
 
 @interface FakeSaveToDriveCommandsHandler : NSObject <SaveToDriveCommands>
@@ -50,6 +74,8 @@
   void SetUp() final {
     PlatformTest::SetUp();
     web_state_ = std::make_unique<web::FakeWebState>();
+    DriveTabHelper::CreateForWebState(web_state_.get());
+    FakeDownloadManagerTabHelper::CreateForWebState(web_state_.get());
     download_task_ =
         std::make_unique<web::FakeDownloadTask>(GURL(kTestUrl), kTestMimeType);
     download_task_->SetWebState(web_state_.get());
@@ -68,6 +94,15 @@
     PlatformTest::TearDown();
   }
 
+  DriveTabHelper* GetDriveTabHelper() const {
+    return DriveTabHelper::FromWebState(web_state_.get());
+  }
+
+  FakeDownloadManagerTabHelper* GetDownloadManagerTabHelper() const {
+    return static_cast<FakeDownloadManagerTabHelper*>(
+        DownloadManagerTabHelper::FromWebState(web_state_.get()));
+  }
+
   std::unique_ptr<web::FakeWebState> web_state_;
   std::unique_ptr<web::FakeDownloadTask> download_task_;
   FakeSaveToDriveCommandsHandler* save_to_drive_commands_handler_;
@@ -98,3 +133,23 @@
   web_state_->WasHidden();
   EXPECT_EQ(nullptr, save_to_drive_commands_handler_.presentedDownloadTask);
 }
+
+// Tests that the `DownloadManagerTabHelper` is informed and that the
+// `DownloadTask` and the selected identity are sent to the `DriveTabHelper`
+// when `startDownloadAndSaveToDriveWithIdentity:` is invoked.
+TEST_F(SaveToDriveMediatorTest, AddsDownloadToSaveToDrive) {
+  id<SystemIdentity> identity = [FakeSystemIdentity fakeIdentity1];
+  EXPECT_EQ(
+      nullptr,
+      GetDownloadManagerTabHelper()->download_task_added_to_save_to_drive_);
+  EXPECT_EQ(std::nullopt,
+            GetDriveTabHelper()->GetDownloadTaskSaveToDriveData());
+  [mediator_ startDownloadAndSaveToDriveWithIdentity:identity];
+  EXPECT_EQ(
+      download_task_.get(),
+      GetDownloadManagerTabHelper()->download_task_added_to_save_to_drive_);
+  DownloadTaskSaveToDriveData expected_save_to_drive_data{
+      .task = download_task_.get(), .identity = identity};
+  EXPECT_EQ(expected_save_to_drive_data,
+            GetDriveTabHelper()->GetDownloadTaskSaveToDriveData());
+}
diff --git a/ios/chrome/browser/variations/model/ios_chrome_variations_service_client.h b/ios/chrome/browser/variations/model/ios_chrome_variations_service_client.h
index 88f0e5c7..8d279325 100644
--- a/ios/chrome/browser/variations/model/ios_chrome_variations_service_client.h
+++ b/ios/chrome/browser/variations/model/ios_chrome_variations_service_client.h
@@ -44,7 +44,8 @@
       PrefService* local_state) override;
   std::unique_ptr<variations::SeedResponse>
   TakeSeedFromNativeVariationsSeedStore() override;
-  void RegisterLimitedEntropySyntheticTrial(std::string_view group_name);
+  void RegisterLimitedEntropySyntheticTrial(
+      std::string_view group_name) override;
 };
 
 #endif  // IOS_CHROME_BROWSER_VARIATIONS_MODEL_IOS_CHROME_VARIATIONS_SERVICE_CLIENT_H_
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 05affd6..8cbfa3d9c 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -213,6 +213,7 @@
     "//ios/chrome/browser/device_sharing/model:unit_tests",
     "//ios/chrome/browser/download/model:unit_tests",
     "//ios/chrome/browser/download/model/background_service:unit_tests",
+    "//ios/chrome/browser/drive/model:unit_tests",
     "//ios/chrome/browser/enterprise/model/idle:unit_tests",
     "//ios/chrome/browser/favicon:unit_tests",
     "//ios/chrome/browser/feature_engagement/model:unit_tests",
diff --git a/ios/chrome/test/fakes/fake_download_manager_tab_helper_delegate.mm b/ios/chrome/test/fakes/fake_download_manager_tab_helper_delegate.mm
index 961cfab2..8b5a782e 100644
--- a/ios/chrome/test/fakes/fake_download_manager_tab_helper_delegate.mm
+++ b/ios/chrome/test/fakes/fake_download_manager_tab_helper_delegate.mm
@@ -30,29 +30,33 @@
   return YES;
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-               didCreateDownload:(nonnull web::DownloadTask*)download
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+               didCreateDownload:(web::DownloadTask*)download
                webStateIsVisible:(BOOL)webStateIsVisible {
   if (webStateIsVisible) {
     _state = std::make_unique<web::DownloadTask::State>(download->GetState());
   }
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-         decidePolicyForDownload:(nonnull web::DownloadTask*)download
-               completionHandler:(nonnull void (^)(NewDownloadPolicy))handler {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+         decidePolicyForDownload:(web::DownloadTask*)download
+               completionHandler:(void (^)(NewDownloadPolicy))handler {
   _decidingPolicyForDownload = download;
   _decidePolicyForDownloadHandler = handler;
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didHideDownload:(nonnull web::DownloadTask*)download {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didHideDownload:(web::DownloadTask*)download {
   _state = nullptr;
 }
 
-- (void)downloadManagerTabHelper:(nonnull DownloadManagerTabHelper*)tabHelper
-                 didShowDownload:(nonnull web::DownloadTask*)download {
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+                 didShowDownload:(web::DownloadTask*)download {
   _state = std::make_unique<web::DownloadTask::State>(download->GetState());
 }
 
+- (void)downloadManagerTabHelper:(DownloadManagerTabHelper*)tabHelper
+     didAddDownloadToSaveToDrive:(web::DownloadTask*)download {
+}
+
 @end
diff --git a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
index f4d12ff..e1d6d21f9 100644
--- a/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-607f9a03fbe08b2baa82a09446c6609a92227783
\ No newline at end of file
+30f62dbb368c351fdae4a63622c50972f281b00d
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1 b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
index d940f24..60f82636 100644
--- a/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
+++ b/ios/google_internal/frameworks/ChromeSSOInternal.framework.dSYM.ios.zip.sha1
@@ -1 +1 @@
-d40f863a1cfa7984dad2b8867eeec77ed5019963
\ No newline at end of file
+37a9a2a967a40a90c6ad074f0cf0fb0fa8ca4659
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index aab5af6..18fd1156 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-47fb8fc8dc83bfce18205fbb74647cd602568e2a
\ No newline at end of file
+0b47969c12273a3d8bcd6f79a48b4b56a4d3550c
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 25e9b51f1..f6242da7 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-fee2078398ca84bb80b69b8badf51a6156909de9
\ No newline at end of file
+c42203504701bb25376b8e7a78c27556670daaf7
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index c565381b..c6de7b3e 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-acc07487014e69cf960cddc5b8f14b07685e4748
\ No newline at end of file
+6875517a4544636bcb67810b87b8aa375d72b375
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index a8c60a1..e514ab2 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-518073c8ac26df23aa59be27f7c9de2564f3f5d5
\ No newline at end of file
+3413b568962786d714702f352871d0c3f76db402
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
index 867f926..2956d64 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-e883eb059a99c8eee94c85d130400fe388842576
\ No newline at end of file
+575111da51ddf1d10694794586547a89275038f8
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
index 715f46f..f95e2c3 100644
--- a/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_test_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-71fe66266a5ab89d94bc94061cc9c2b9df491956
\ No newline at end of file
+45756f3d04551632efcfb82da56b8bedfa6fb200
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 5de62f3ae..6474031 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-eb5cc48085b7087000acaf4e876a86b8d92bf99e
\ No newline at end of file
+4057b0bac9e5d413e5cf98ee03cf4e65b032615a
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index c42c7d7..28fdd7d 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-20ce80e8664906a9f04765c075f969a2b309f348
\ No newline at end of file
+a42d13114dc06dc1c37b46ee0098143fa265667f
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 4e97b41..614a69d2 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-86e485a1d983542604981ba62647acc7bc63840a
\ No newline at end of file
+5b6c00d9de560c742951aeef9145e75078a620a6
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index 0b8a544..e7a17ffed 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-2703cdf6dccef30552e3953e817421e1dd524613
\ No newline at end of file
+0bfa8c3db742b42c0fead14bc9a173e851b335aa
\ No newline at end of file
diff --git a/ios_internal b/ios_internal
index b41b25f..34c58e6 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit b41b25ff5c18d9589ef7a1e8e2bec24bffc75acc
+Subproject commit 34c58e6d6072333decae4e8c0b3b93316920f550
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index f268b27..0cc8642e 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -195,8 +195,6 @@
     "key_system_names.h",
     "key_systems.cc",
     "key_systems.h",
-    "libvpx_thread_wrapper.cc",
-    "libvpx_thread_wrapper.h",
     "localized_strings.cc",
     "localized_strings.h",
     "logging_override_if_enabled.h",
@@ -417,6 +415,10 @@
   }
 
   if (media_use_libvpx) {
+    sources += [
+      "libvpx_thread_wrapper.cc",
+      "libvpx_thread_wrapper.h",
+    ]
     deps += [ "//third_party/libvpx" ]
   }
 
diff --git a/media/base/media.cc b/media/base/media.cc
index d3a378c8..d7d066d 100644
--- a/media/base/media.cc
+++ b/media/base/media.cc
@@ -28,9 +28,11 @@
 
 namespace media {
 
+#if BUILDFLAG(ENABLE_LIBVPX)
 BASE_FEATURE(kLibvpxUseChromeThreads,
              "LibvpxUseChromeThreads",
              base::FEATURE_DISABLED_BY_DEFAULT);
+#endif  // BUILDFLAG(ENABLE_LIBVPX)
 
 // Media must only be initialized once; use a thread-safe static to do this.
 class MediaInitializer {
@@ -54,9 +56,11 @@
 
 #endif  // BUILDFLAG(ENABLE_FFMPEG)
 
+#if BUILDFLAG(ENABLE_LIBVPX)
     if (base::FeatureList::IsEnabled(kLibvpxUseChromeThreads)) {
       InitLibVpxThreadWrapper();
     }
+#endif  // BUILDFLAG(ENABLE_LIBVPX)
   }
 
   MediaInitializer(const MediaInitializer&) = delete;
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index adadcab..157d6ec 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2023-12-26 12:55 UTC
+# Last updated: 2023-12-27 12:54 UTC
 PinsListTimestamp
-1703595330
+1703681693
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json
index 9ffff560..29b9a85 100644
--- a/net/http/transport_security_state_static_pins.json
+++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@
 // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets'
 // refer to, and the timestamp at which the pins list was last updated.
 //
-// Last updated: 2023-12-26 12:55 UTC
+// Last updated: 2023-12-27 12:54 UTC
 //
 {
   "pinsets": [
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e103b7d..a7dce8f3 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -15030,6 +15030,25 @@
             ]
         }
     ],
+    "RendererMainIsNormalThreadTypeForWebRTC": [
+        {
+            "platforms": [
+                "linux",
+                "chromeos",
+                "chromeos_lacros",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "RendererMainIsNormalThreadTypeForWebRTC"
+                    ]
+                }
+            ]
+        }
+    ],
     "ReportCertificateErrors": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index e939f9a9..0ddebba 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit e939f9a9cf76533d3ddaa1bb279813708e7e7d94
+Subproject commit 0ddebba548bf85ae807d1d7d3641bd79f7a189b2
diff --git a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
index 1234a3bf..46b8eb9 100644
--- a/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_network_agent.cc
@@ -123,8 +123,8 @@
 constexpr int kDefaultTotalBufferSize = 10 * 1000 * 1000;    // 10 MB
 constexpr int kDefaultResourceBufferSize = 5 * 1000 * 1000;  // 5 MB
 #else
-constexpr int kDefaultTotalBufferSize = 100 * 1000 * 1000;    // 100 MB
-constexpr int kDefaultResourceBufferSize = 10 * 1000 * 1000;  // 10 MB
+constexpr int kDefaultTotalBufferSize = 200 * 1000 * 1000;    // 200 MB
+constexpr int kDefaultResourceBufferSize = 20 * 1000 * 1000;  // 20 MB
 #endif
 
 // Pattern may contain stars ('*') which match to any (possibly empty) string.
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index 0b13e41d..dd5c96e 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -1364,7 +1364,6 @@
 crbug.com/626703 [ Debug ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
 crbug.com/626703 [ Linux Release ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
 crbug.com/626703 [ Mac11 Release ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
-crbug.com/626703 [ Mac11-arm64 Release ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
 crbug.com/626703 [ Mac12 Release ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
 crbug.com/626703 [ Mac13 Release ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
 crbug.com/626703 [ Release Win ] external/wpt/html/semantics/forms/form-submission-0/multipart-formdata.window.html [ Slow ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e084829..6f72bbf8 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2686,11 +2686,9 @@
 crbug.com/626703 [ Mac12 ] external/wpt/performance-timeline/back-forward-cache-restoration.tentative.html [ Timeout ]
 crbug.com/626703 [ Mac12 ] virtual/pna-navigations-warning/external/wpt/fetch/private-network-access/shared-worker-fetch.tentative.https.window.html [ Timeout ]
 crbug.com/626703 [ Mac12 ] virtual/prefetch-no-vary-search/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single-with-hint.https.html?3-3 [ Timeout ]
-crbug.com/626703 [ Mac12 ] virtual/prefetch-reusable/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single-with-hint.https.html?27-27 [ Timeout ]
 crbug.com/626703 [ Mac12 ] virtual/prefetch/external/wpt/speculation-rules/prefetch/no-vary-search/prefetch-single.https.html?15-15 [ Timeout ]
 crbug.com/626703 external/wpt/css/css-backgrounds/border-image-image-type-001.htm [ Failure ]
 crbug.com/626703 virtual/threaded/external/wpt/css/css-backgrounds/border-image-image-type-001.htm [ Failure ]
-crbug.com/626703 [ Mac13 ] virtual/threaded/external/wpt/css/css-backgrounds/border-image-image-type-004.htm [ Failure ]
 crbug.com/626703 [ Mac10.15 ] virtual/threaded/external/wpt/css/css-backgrounds/border-image-image-type-004.htm [ Failure Timeout ]
 crbug.com/626703 [ Mac10.15 ] virtual/threaded/external/wpt/css/css-backgrounds/border-image-image-type-005.htm [ Failure Timeout ]
 crbug.com/626703 external/wpt/css/css-contain/content-visibility/content-visibility-on-g.html [ Failure ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 6d3aec2..14956e1 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -294809,6 +294809,10 @@
      }
     },
     "nonce-hiding": {
+     "dangling-html-or-body.html.headers": [
+      "67d4c81e589d487b25bdb73a89f8ba1c49a0c431",
+      []
+     ],
      "nonces.html.headers": [
       "daf482b5aba4ff052b94c99f422910727c600aae",
       []
@@ -415011,7 +415015,7 @@
     ],
     "import_export": {
      "ec_importKey.https.any.js": [
-      "31f062e313f6fe9dc735fc9a5588df0c098fde98",
+      "a01bfbb0ef2e1881e83aaefe76f1ca39805c9304",
       [
        "WebCryptoAPI/import_export/ec_importKey.https.any.html",
        {
@@ -431962,6 +431966,13 @@
      ]
     },
     "nonce-hiding": {
+     "dangling-html-or-body.html": [
+      "4ba65e05b885cbc780fab7f1650689016f257ac2",
+      [
+       null,
+       {}
+      ]
+     ],
      "nonce-hiding-move-document.html": [
       "49de893ba03fbd25125fcf13eff8c352e4992d85",
       [
@@ -557016,7 +557027,7 @@
     },
     "dom": {
      "aria-attribute-reflection.html": [
-      "e201d4660eb754b9560d1b48f22dd2c063d04658",
+      "8970938ac92162ecaeb0608d8bc42905fb2818f7",
       [
        null,
        {}
diff --git a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js
index 31f062e..a01bfbb 100644
--- a/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/WebCryptoAPI/import_export/ec_importKey.https.any.js
@@ -80,6 +80,9 @@
                         }
 
                         testFormat(format, algorithm, data, curve, usages, extractable);
+                        if (vector.name === 'ECDH' && format === 'jwk') {
+                            testEcdhJwkAlg(algorithm, { ...data.jwk, alg: 'any alg works here' }, curve, usages, extractable);
+                        }
                     });
 
                 });
@@ -90,11 +93,13 @@
                     var data = keyData[curve];
                     allValidUsages(vector.privateUsages).forEach(function(usages) {
                         testFormat(format, algorithm, data, curve, usages, extractable);
+                        if (vector.name === 'ECDH' && format === 'jwk') {
+                            testEcdhJwkAlg(algorithm, { ...data.jwk, alg: 'any alg works here' }, curve, usages, extractable);
+                        }
                     });
                     testEmptyUsages(format, algorithm, data, curve, extractable);
                 });
             });
-
         });
     });
 
@@ -151,6 +156,21 @@
         }, "Empty Usages: " + keySize.toString() + " bits " + parameterString(format, false, keyData, algorithm, extractable, usages));
     }
 
+    // Test ECDH importKey with a JWK format
+    // Should succeed with any "alg" value
+    function testEcdhJwkAlg(algorithm, keyData, keySize, usages, extractable) {
+        const format = "jwk";
+        promise_test(function(test) {
+            return subtle.importKey(format, keyData, algorithm, extractable, usages).
+            then(function(key) {
+                assert_equals(key.constructor, CryptoKey, "Imported a CryptoKey object");
+                assert_goodCryptoKey(key, algorithm, extractable, usages, keyData.d ? 'private' : 'public');
+            }, function(err) {
+                assert_unreached("Threw an unexpected error: " + err.toString());
+            });
+        }, "ECDH any JWK alg: " + keySize.toString() + " bits " + parameterString(format, false, keyData, algorithm, extractable, usages));
+    }
+
 
 
     // Helper methods follow:
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html
new file mode 100644
index 0000000..4ba65e0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js" nonce="secret"></script>
+<script src="/resources/testharnessreport.js" nonce="secret"></script>
+
+<!-- `Content-Security-Policy: script-src 'nonce-secret'` delivered via headers -->
+
+<body>
+  <style>body[nonce*=secret]{background:url(/security/resources/abe.png);}</style>
+  <body
+  <script nonce="secret" src="https://example.com/good.js"></script>
+  <script nonce="secret">
+    test(t => {
+      const body = document.querySelector('body');
+      var style = getComputedStyle(body);
+      assert_equals(style['background-image'], 'none');
+    }, "Nonces don't leak via CSS side-channels when a dangling body is injected.");
+  </script>
+
+  <style>html[nonce*=secret]{background:url(/security/resources/abe.png);}</style>
+  <html
+  <script nonce="secret" src="https://example.com/good.js"></script>
+  <script nonce="secret">
+    test(t => {
+      const html = document.querySelector('html');
+      var style = getComputedStyle(html);
+      assert_equals(style['background-image'], 'none');
+    }, "Nonces don't leak via CSS side-channels when a dangling html is injected.");
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html.headers b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html.headers
new file mode 100644
index 0000000..67d4c81
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/content-security-policy/nonce-hiding/dangling-html-or-body.html.headers
@@ -0,0 +1 @@
+Content-Security-Policy: script-src 'nonce-secret'
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/aria-attribute-reflection.html b/third_party/blink/web_tests/external/wpt/html/dom/aria-attribute-reflection.html
index e201d46..8970938a 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/aria-attribute-reflection.html
+++ b/third_party/blink/web_tests/external/wpt/html/dom/aria-attribute-reflection.html
@@ -8,9 +8,16 @@
 
 <script>
 function testNullable(element, jsAttr, contentAttr) {
+    var originalValue = element[jsAttr];
+    assert_false(originalValue === null);
     element[jsAttr] = null;
     assert_equals(element[jsAttr], null);
     assert_false(element.hasAttribute(contentAttr));
+    // Setting to undefined results in same state as setting to null.
+    element[jsAttr] = originalValue;
+    element[jsAttr] = undefined;
+    assert_equals(element[jsAttr], null);
+    assert_false(element.hasAttribute(contentAttr));
 }
 </script>
 
diff --git a/third_party/dawn b/third_party/dawn
index a1a25d6..e9f87b6 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit a1a25d682c7d3ed4206b4b40e6c36f82bfcfa0ea
+Subproject commit e9f87b69b96384c508aa2d408601e0881035ff3a
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index c6bf12d..63790d3a 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit c6bf12d0f4fd78aa7dbc7e5379b2cb7c2503b58b
+Subproject commit 63790d3a0292fcede670a1a0259e9356916c9bd6
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index a89ba04..5eb9e24 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit a89ba04022e79cb59bdcdf510392cd2e6cd749b3
+Subproject commit 5eb9e244097ff37b64738ca222787348f6f05920
diff --git a/third_party/webrtc b/third_party/webrtc
index 3cce50a..ee2fcba 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 3cce50ae4bc4384dd61b7d2dc877203993b09b19
+Subproject commit ee2fcbab428934630b39e0be128cb0fcd0573aae
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index 23f46fa..98769c24 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -506,7 +506,7 @@
     "META": {"sizes": {"includes": [50]}},
     "includes": [4280],
   },
-  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/internet_config_dialog/internet_config_dialog_resources.grd": {
+  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/internet_config_dialog/resources.grd": {
     "META": {"sizes": {"includes": [10],}},
     "includes": [4300],
   },
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 6815365..2b8235fec 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -13445,6 +13445,15 @@
   <description>User started the download in Download Manager.</description>
 </action>
 
+<action name="IOSDownloadStartDownloadToDrive">
+  <owner>qpubert@google.com</owner>
+  <owner>ewannpv@chromium.org</owner>
+  <description>
+    User started the download in Download Manager, with Drive as destination for
+    their download.
+  </description>
+</action>
+
 <action name="IOSDownloadTryCloseWhenInProgress">
   <owner>ewannpv@chromium.org</owner>
   <owner>sdefresne@chromium.org</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 11ce672..5edd63d 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -27330,7 +27330,6 @@
   <int value="-1259901957" label="VrBrowserKeyboard:disabled"/>
   <int value="-1259809702" label="Enable16Desks:enabled"/>
   <int value="-1259627326" label="AllowRepeatedUpdates:disabled"/>
-  <int value="-1258793847" label="MagicStackAndroid:disabled"/>
   <int value="-1258141852" label="ScrollUnification:enabled"/>
   <int value="-1257822114" label="ContextMenuPopupForAllScreenSizes:disabled"/>
   <int value="-1256823053" label="WebRtcHWVP9Encoding:enabled"/>
@@ -31962,7 +31961,6 @@
   <int value="940705998" label="CrOSLateBootArcSwitchToKeyMintDaemon:disabled"/>
   <int value="940751405" label="IppFirstSetupForUsbPrinters:enabled"/>
   <int value="941036016" label="ContentSuggestionsSettings:disabled"/>
-  <int value="941703796" label="MagicStackAndroid:enabled"/>
   <int value="941883332" label="ProactiveTabFreezeAndDiscard:disabled"/>
   <int value="941948340" label="PlaybackSpeedButton:enabled"/>
   <int value="942357311" label="UpcomingSharingFeatures:disabled"/>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 8c82a224..c41cd365b 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1995,6 +1995,22 @@
 </histogram>
 
 <histogram
+    name="Arc.Runtime.Performance.PerceivedFPS2{ArcPerformanceAppCategories}"
+    units="fps" expires_after="2024-05-05">
+  <owner>khmel@google.com</owner>
+  <owner>matvore@google.com</owner>
+  <summary>
+    Render frames per second, not considering frames which exo could not
+    present. Only collected if the user has app syncing enabled and doesn't have
+    a custom passphrase set. {ArcPerformanceAppCategories}
+  </summary>
+  <token key="ArcPerformanceAppCategories"
+      variants="ArcPerformanceAppCategories">
+    <variant name=""/>
+  </token>
+</histogram>
+
+<histogram
     name="Arc.Runtime.Performance.RenderQuality2{ArcPerformanceAppCategories}"
     units="%" expires_after="2024-05-05">
   <owner>khmel@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos/enums.xml b/tools/metrics/histograms/metadata/chromeos/enums.xml
index 2fd7a34..7ee6309 100644
--- a/tools/metrics/histograms/metadata/chromeos/enums.xml
+++ b/tools/metrics/histograms/metadata/chromeos/enums.xml
@@ -855,6 +855,24 @@
   <int value="5" label="RemoveReplug"/>
 </enum>
 
+<enum name="FirmwareUpdateFwupdStatus">
+  <int value="0" label="Unknown"/>
+  <int value="1" label="Idle"/>
+  <int value="2" label="Loading"/>
+  <int value="3" label="Decompressing"/>
+  <int value="4" label="DeviceRestart"/>
+  <int value="5" label="DeviceWrite"/>
+  <int value="6" label="DeviceVerify"/>
+  <int value="7" label="Scheduling"/>
+  <int value="8" label="Downloading"/>
+  <int value="9" label="DeviceRead"/>
+  <int value="10" label="DeviceErase"/>
+  <int value="11" label="WaitingForAuth"/>
+  <int value="12" label="DeviceBusy"/>
+  <int value="13" label="Shutdown"/>
+  <int value="14" label="WaitingForUser"/>
+</enum>
+
 <enum name="FirmwareUpdateInstallResult">
   <int value="0" label="Success"/>
   <int value="1" label="InstallFailed"/>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index bd5f8f1..3a8663f 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -1194,6 +1194,18 @@
   <token key="FeatureName" variants="FeaturesLoggingUsageEvents"/>
 </histogram>
 
+<histogram name="ChromeOS.FirmwareUpdateUi.InstallFailedWithStatus"
+    enum="FirmwareUpdateFwupdStatus" expires_after="2024-06-28">
+  <owner>cambickel@google.com</owner>
+  <owner>jimmyxgong@chromium.org</owner>
+  <owner>zentaro@chromium.org</owner>
+  <owner>cros-peripherals@google.com</owner>
+  <summary>
+    Records the most recent FwupdStatus when a firmware install fails for any
+    reason.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.FirmwareUpdateUi.InstallResult"
     enum="FirmwareUpdateInstallResult" expires_after="2024-04-28">
   <owner>jimmyxgong@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
index 22014d6..c765a4b 100644
--- a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
@@ -97,7 +97,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.BlurredWindowDuration" units="ms"
-    expires_after="2024-02-04">
+    expires_after="2024-12-27">
   <owner>wesokuhara@google.com</owner>
   <owner>xiaohuic@chromium.org</owner>
   <owner>cros-settings@google.com</owner>
@@ -522,7 +522,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.SearchLatency" units="ms"
-    expires_after="2024-02-04">
+    expires_after="2024-12-27">
   <owner>wesokuhara@google.com</owner>
   <owner>xiaohuic@chromium.org</owner>
   <owner>cros-settings@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 65845105..1e8f523 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -295,8 +295,8 @@
 </histogram>
 
 <histogram name="CustomTabs.IncognitoCCTCallerId" enum="IncognitoCCTCallerId"
-    expires_after="2024-02-04">
-  <owner>roagarwal@chromium.org</owner>
+    expires_after="2024-08-04">
+  <owner>arabm@google.com</owner>
   <owner>chrome-incognito@google.com</owner>
   <owner>cct-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 9cc2b2a..39138ca3 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -58,17 +58,6 @@
   </summary>
 </histogram>
 
-<histogram name="History.Backend.TransactionBeginSuccess" enum="BooleanSuccess"
-    expires_after="2023-10-01">
-  <owner>tommycli@chromium.org</owner>
-  <owner>chrome-journeys@google.com</owner>
-  <component>UI&gt;Browser&gt;History</component>
-  <summary>
-    Recorded after attempting to begin a singleton transaction within
-    HistoryBackend. Records whether it was successfully begun or not.
-  </summary>
-</histogram>
-
 <histogram name="History.Backend.TransactionCommitError"
     enum="SqliteLoggedResultCode" expires_after="2024-02-04">
   <owner>tommycli@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index c1e156b..8f60e605 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -989,7 +989,7 @@
 </histogram>
 
 <histogram name="Navigation.MainFrameScheme2" enum="NavigationScheme"
-    expires_after="2024-01-15">
+    expires_after="2024-06-15">
   <owner>estark@chromium.org</owner>
   <owner>elawrence@chromium.org</owner>
   <owner>trusty-transport@chromium.org</owner>
@@ -1042,7 +1042,7 @@
 </histogram>
 
 <histogram name="Navigation.MainFrameSchemeOTR2" enum="NavigationScheme"
-    expires_after="2024-01-15">
+    expires_after="2024-06-15">
   <owner>elawrence@chromium.org</owner>
   <owner>estark@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 1152122..78c5f3a0 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -3066,7 +3066,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.AdapterAllowlisted"
-    enum="WiFiAdapterInAllowlist" expires_after="2024-04-28">
+    enum="WiFiAdapterInAllowlist" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3077,7 +3077,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Ap80211kSupport" enum="WiFiAp80211kSupport"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3087,7 +3087,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Ap80211rSupport" enum="WiFiAp80211rSupport"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3097,7 +3097,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Ap80211vBSSMaxIdlePeriodSupport"
-    enum="WiFiAp80211vBSSMaxIdlePeriodSupport" expires_after="2024-12-01">
+    enum="WiFiAp80211vBSSMaxIdlePeriodSupport" expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3108,7 +3108,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Ap80211vBSSTransitionSupport"
-    enum="WiFiAp80211vBSSTransitionSupport" expires_after="2024-12-01">
+    enum="WiFiAp80211vBSSTransitionSupport" expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3119,7 +3119,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Ap80211vDMSSupport"
-    enum="WiFiAp80211vDMSSupport" expires_after="2024-02-04">
+    enum="WiFiAp80211vDMSSupport" expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3129,7 +3129,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ApAlternateEDCASupport"
-    enum="WiFiApAlternateEDCASupport" expires_after="2024-05-05">
+    enum="WiFiApAlternateEDCASupport" expires_after="2024-12-31">
   <owner>damiendejean@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3140,7 +3140,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ApChannelSwitch" enum="WiFiApChannelSwitch"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3150,7 +3150,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ApDisconnectReason" enum="WiFiReasonCode"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3160,7 +3160,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ApDisconnectType" enum="WiFiStatusType"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3170,7 +3170,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ApSCSupport" enum="WiFiApSCSupport"
-    expires_after="2024-05-05">
+    expires_after="2024-12-31">
   <owner>damiendejean@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3180,7 +3180,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.AssocFailureType" enum="WiFiStatusCode"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3192,7 +3192,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.AuthFailureType" enum="WiFiStatusCode"
-    expires_after="2024-05-19">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3204,7 +3204,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.AutoConnectableServices" units="units"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>stevenjb@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
@@ -3215,7 +3215,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.AvailableBSSesAtConnect" units="units"
-    expires_after="2024-02-12">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3225,7 +3225,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.BadPassphraseServiceType"
-    enum="WiFiBadPassphraseServiceType" expires_after="2024-02-04">
+    enum="WiFiBadPassphraseServiceType" expires_after="2024-12-31">
   <owner>yichenyu@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3236,7 +3236,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.BSSTransitionManagementSupport"
-    enum="WiFiBSSTransitionManagementSupport" expires_after="2024-12-01">
+    enum="WiFiBSSTransitionManagementSupport" expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3247,7 +3247,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.Channel" enum="NetworkChannelType"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3257,7 +3257,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.CiscoAdaptiveFTSupport"
-    enum="WiFiCiscoAdaptiveFTSupport" expires_after="2024-04-28">
+    enum="WiFiCiscoAdaptiveFTSupport" expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3268,7 +3268,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ClientDisconnectReason"
-    enum="WiFiReasonCode" expires_after="2024-04-28">
+    enum="WiFiReasonCode" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3278,7 +3278,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ClientDisconnectType" enum="WiFiStatusType"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3288,7 +3288,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.CQMNotification"
-    enum="WiFiCQMNotificationType" expires_after="2024-04-28">
+    enum="WiFiCQMNotificationType" expires_after="2024-12-31">
   <owner>kuabhs@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3300,7 +3300,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.DevicePresenceStatus" enum="BooleanPresent"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3310,7 +3310,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.Disconnect" enum="NetworkDisconnectType"
-    expires_after="2024-05-12">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3320,7 +3320,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.EapInnerProtocol" enum="EAPInnerProtocol"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3330,7 +3330,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.EapOuterProtocol" enum="EAPOuterProtocol"
-    expires_after="2024-02-04">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3340,7 +3340,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.ExpiredLeaseLengthSeconds2" units="seconds"
-    expires_after="2024-02-04">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3351,7 +3351,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.FallbackDNSTestResult"
-    enum="FallbackDNSTestResult" expires_after="2024-12-01">
+    enum="FallbackDNSTestResult" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3362,7 +3362,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Hidden.EverConnected"
-    enum="BooleanConnected" expires_after="2024-04-28">
+    enum="BooleanConnected" expires_after="2024-12-31">
   <owner>jonmann@chromium.org</owner>
   <owner>tnagel@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
@@ -3377,7 +3377,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.Hidden.LastConnected" units="days"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>jonmann@chromium.org</owner>
   <owner>tnagel@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
@@ -3391,7 +3391,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.HiddenSSIDEverConnected"
-    enum="BooleanEverConnected" expires_after="2024-05-12">
+    enum="BooleanEverConnected" expires_after="2024-12-31">
   <owner>tnagel@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <owner>cros-privacy-core@google.com</owner>
@@ -3404,7 +3404,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.HiddenSSIDNetworkCount" units="units"
-    expires_after="2024-05-19">
+    expires_after="2024-12-31">
   <owner>tnagel@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <owner>cros-privacy-core@google.com</owner>
@@ -3419,7 +3419,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.HS20Support" enum="HotspotSupport"
-    expires_after="2024-01-14">
+    expires_after="2024-12-31">
   <owner>kglund@google.com</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3431,7 +3431,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.MBOSupport" enum="MBOSupport"
-    expires_after="2024-02-12">
+    expires_after="2024-12-31">
   <owner>matthewmwang@google.com</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3453,7 +3453,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.NetworkConnectionIPType"
-    enum="NetworkConnectionIPType" expires_after="2024-02-25">
+    enum="NetworkConnectionIPType" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3463,7 +3463,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.NetworkProblemDetected"
-    enum="NetworkProblemType" expires_after="2024-12-01">
+    enum="NetworkProblemType" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3634,7 +3634,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.PhyMode" enum="NetworkPhyModeType"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3648,7 +3648,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.PortalResult" enum="NetworkPortalResult"
-    expires_after="2024-05-12">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>stevenjb@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
@@ -3659,7 +3659,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.RegulatoryDomain" enum="RegulatoryDomain"
-    expires_after="2024-02-04">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3670,7 +3670,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.RememberedNetworkCount" units="units"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3682,7 +3682,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.RememberedSystemNetworkCount" units="units"
-    expires_after="2024-05-19">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3698,7 +3698,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.RememberedUserNetworkCount" units="units"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3709,7 +3709,7 @@
 </histogram>
 
 <histogram base="true" name="Network.Shill.WiFi.RoamComplete"
-    enum="WiFiRoamComplete" expires_after="2024-05-19">
+    enum="WiFiRoamComplete" expires_after="2024-12-31">
 <!-- Name completed by histogram_suffixes name="RoamSecurityType" -->
 
   <owner>matthewmwang@chromium.org</owner>
@@ -3722,7 +3722,7 @@
 </histogram>
 
 <histogram base="true" name="Network.Shill.WiFi.RoamTime" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
 <!-- Name completed by histogram_suffixes name="RoamSecurityType" -->
 
   <owner>matthewmwang@chromium.org</owner>
@@ -3735,7 +3735,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ScanResult" enum="WiFiScanResult"
-    expires_after="2024-12-24">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3745,7 +3745,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.ScanTimeInEbusy" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3755,7 +3755,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.Security" enum="NetworkSecurityType"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3765,7 +3765,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.SecurityChange"
-    enum="NetworkSecurityChange" expires_after="2024-04-28">
+    enum="NetworkSecurityChange" expires_after="2024-12-31">
   <owner>andrzejo@google.com</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3778,7 +3778,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.ServiceErrors" enum="NetworkServiceError"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>kuabhs@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3787,7 +3787,7 @@
 </histogram>
 
 <histogram base="true" name="Network.Shill.WiFi.SessionLength" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
 <!-- Name completed by histogram_suffixes name="RoamSecurityType" -->
 
   <owner>matthewmwang@chromium.org</owner>
@@ -3800,7 +3800,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.SessionTagState.{Event}"
-    enum="WiFiSessionTagState" expires_after="2024-04-28">
+    enum="WiFiSessionTagState" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>druth@google.com</owner>
   <owner>cros-network-metrics@google.com</owner>
@@ -3818,7 +3818,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.SignalAtDisconnect" units="negative dBm"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3828,7 +3828,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.SignalStrength" units="negative dBm"
-    expires_after="2024-02-25">
+    expires_after="2024-12-31">
   <owner>kuabhs@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3838,7 +3838,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.SupplicantAttempts" units="attempts"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3855,7 +3855,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.TimeFromRekeyToFailureSeconds"
-    units="seconds" expires_after="2024-03-17">
+    units="seconds" expires_after="2024-12-31">
   <owner>billyzhao@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3867,7 +3867,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeOnline" units="seconds"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3878,7 +3878,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeResumeToReady" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3889,7 +3889,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.TimeResumeToReadyHB" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3901,7 +3901,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.TimeResumeToReadyLB" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3913,7 +3913,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.TimeResumeToReadyUHB" units="ms"
-    expires_after="2024-12-01">
+    expires_after="2024-12-31">
   <owner>matthewmwang@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3925,7 +3925,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToConfig" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3935,7 +3935,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToConnect" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3946,7 +3946,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToInitialize" units="ms"
-    expires_after="2024-02-04">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3956,7 +3956,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToJoin" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -3966,7 +3966,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToOnline" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4000,7 +4000,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToScan" units="ms"
-    expires_after="2024-05-26">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4010,7 +4010,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.TimeToScanAndConnect" units="ms"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4022,7 +4022,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.TransmitBitrateMbps" units="Mbps"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4033,7 +4033,7 @@
 </histogram>
 
 <histogram name="Network.Shill.Wifi.UnreliableLinkSignalStrength" units="units"
-    expires_after="2024-04-28">
+    expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4044,7 +4044,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.UserInitiatedConnectionFailureReason"
-    enum="ConnectionFailureReason" expires_after="2024-05-19">
+    enum="ConnectionFailureReason" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
@@ -4056,7 +4056,7 @@
 </histogram>
 
 <histogram name="Network.Shill.WiFi.UserInitiatedConnectionResult"
-    enum="ConnectionResult" expires_after="2024-04-28">
+    enum="ConnectionResult" expires_after="2024-12-31">
   <owner>norvez@chromium.org</owner>
   <owner>cros-network-metrics@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index b8d9a09e..e742021ee 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -2916,6 +2916,17 @@
   </summary>
 </histogram>
 
+<histogram name="Conversions.DbVersionOnSourceStored" units="Source"
+    expires_after="2024-04-28">
+  <owner>tquintanilla@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    Records the DB version on every successful source or noised source stored.
+    This metric was added starting with version 56.
+  </summary>
+</histogram>
+
 <histogram
     name="Conversions.DebugReport.HttpResponseOrNetErrorCodeAggregatable"
     enum="CombinedHttpResponseAndNetErrorCode" expires_after="2024-05-10">
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index 5b7ee65..631d09a 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -504,7 +504,7 @@
 </histogram>
 
 <histogram name="Platform.ExternalMetrics.SamplesRead" units="count"
-    expires_after="2024-02-04">
+    expires_after="2025-01-30">
   <owner>iby@google.com</owner>
   <owner>mutexlox@google.com</owner>
   <owner>cros-telemetry@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index 407e80f..ff387fe 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -1646,15 +1646,6 @@
   </summary>
 </histogram>
 
-<histogram name="Signin.SigninReason" enum="SigninReason"
-    expires_after="2024-04-28">
-  <owner>msarda@chromium.org</owner>
-  <owner>bsazonov@chromium.org</owner>
-  <owner>droger@chromium.org</owner>
-  <owner>chrome-signin-team@google.com</owner>
-  <summary>Logs the reason of each completed sign in.</summary>
-</histogram>
-
 <histogram name="Signin.SigninStartedAccessPoint" enum="SigninAccessPoint"
     expires_after="2024-04-28">
   <owner>msarda@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index f46ed6e4..bd522c5 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -863,7 +863,7 @@
 </histogram>
 
 <histogram name="Sync.ModelTypeCommitWithDepletedQuota" enum="SyncModelTypes"
-    expires_after="2024-02-04">
+    expires_after="2024-11-04">
   <owner>rushans@google.com</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 9834304d..95ab1ad 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -42,6 +42,9 @@
       index_in_parent_(index_in_parent),
       unignored_index_in_parent_(unignored_index_in_parent),
       parent_(parent) {
+  // TODO(accessibility): Change to CHECK(tree_) after https://crbug.com/1511053
+  // is fixed.
+  DCHECK(tree_);
   data_.id = id;
 }
 
diff --git a/ui/webui/resources/cr_elements/cr_slider/cr_slider.ts b/ui/webui/resources/cr_elements/cr_slider/cr_slider.ts
index 3527e93..98521ed8 100644
--- a/ui/webui/resources/cr_elements/cr_slider/cr_slider.ts
+++ b/ui/webui/resources/cr_elements/cr_slider/cr_slider.ts
@@ -295,7 +295,9 @@
       return;
     }
 
-    this.getRipple().showAndHoldDown();
+    if (!this.getRipple().holdDown) {
+      this.getRipple().showAndHoldDown();
+    }
     this.showLabel_ = true;
   }
 
diff --git a/v8 b/v8
index 93fbb15..4b00f2f 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 93fbb15abfe4161e4280d20498749b3a1e12415b
+Subproject commit 4b00f2f2737f7c2b1e4ca41ca2dc7c8a55c805d7