diff --git a/DEPS b/DEPS
index 617c4de..5e23fa6 100644
--- a/DEPS
+++ b/DEPS
@@ -304,15 +304,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '4f1cae66791c60dc6584f6cecbd58c533965a7ac',
+  'skia_revision': 'a52ab39fa15fc436cdb6561f46e757646e84e8ab',
   # 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': 'a34243892380ef2bba92a69af3bf7cb422a3755d',
+  'v8_revision': '7e223685b374f8bf8a5c66200f8a285adcd6cd0d',
   # 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': '888ca8d9e307db9936747ca5411a168b5b7669dd',
+  'angle_revision': '74edb4b440b8add4bc60cd7fc597fa020e121e16',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -331,7 +331,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:12.20230322.1.1',
+  'fuchsia_version': 'version:12.20230322.3.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -375,7 +375,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'f6395b16b47ca583919ef1061a65981d3e799c32',
+  'catapult_revision': '018d397758e54d6a6d3b6ddf28a1784664d63f83',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -790,7 +790,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '25a0e75cf4d417f53269c6c6c7aaee7206c6785e',
+    'd645f3b31d367a17c1cb799100fd48799140e210',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -979,7 +979,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'ryrhsKJY8msYy8KMkwB25bMAu68qC6b3CKrKger0aywC',
+          'version': 'nQSF-N2rXShu96xnqkaU5hIWFHc1wK4GXaYJSPYczsYC',
       },
     ],
     'condition': 'checkout_android',
@@ -1887,7 +1887,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'dd474b33f85d63f950fedef2ba889d914c58652d',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '6bdb285e2163e70b8f7d4e698b26dcea357e6616',
+    Var('webrtc_git') + '/src.git' + '@' + 'f8d03d4ca2cb292fb4f770b808b4d5f45067edd3',
 
   # 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 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': Var('chrome_git') + '/chrome/src-internal.git@ca3fc12c31cde3d7b3672864a5fa77bea98f6874',
+    'url': Var('chrome_git') + '/chrome/src-internal.git@f8f27ad0d64e8c6480d4ec2356c8856dfb447070',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index d018d467..a1634ff 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2185,6 +2185,8 @@
     "wm/desks/desk_name_view.h",
     "wm/desks/desk_preview_view.cc",
     "wm/desks/desk_preview_view.h",
+    "wm/desks/desk_textfield.cc",
+    "wm/desks/desk_textfield.h",
     "wm/desks/desks_animations.cc",
     "wm/desks/desks_animations.h",
     "wm/desks/desks_bar_view.cc",
@@ -2195,8 +2197,6 @@
     "wm/desks/desks_histogram_enums.h",
     "wm/desks/desks_restore_util.cc",
     "wm/desks/desks_restore_util.h",
-    "wm/desks/desks_textfield.cc",
-    "wm/desks/desks_textfield.h",
     "wm/desks/desks_util.cc",
     "wm/desks/desks_util.h",
     "wm/desks/expanded_desks_bar_button.cc",
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index 27524947..db3aa62 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -902,26 +902,47 @@
     return;
   }
 
+  // The list of descriptions to be announced.
+  std::vector<std::u16string> descriptions;
+
+  if (item_weak_->is_folder() &&
+      features::IsAppCollectionFolderRefreshEnabled()) {
+    // For folder items, announce the number of apps in the folder.
+    std::u16string app_count_announcement = l10n_util::GetPluralStringFUTF16(
+        IDS_APP_LIST_FOLDER_NUMBER_OF_APPS_ACCESSIBILE_DESCRIPTION,
+        item_weak_->AsFolderItem()->ChildItemCount());
+    descriptions.push_back(app_count_announcement);
+  }
+
   auto app_status = item_weak_->app_status();
+  std::u16string app_status_description;
   switch (app_status) {
     case AppStatus::kBlocked:
-      node_data->SetDescription(
+      app_status_description =
           ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
-              IDS_APP_LIST_BLOCKED_APP));
+              IDS_APP_LIST_BLOCKED_APP);
       break;
     case AppStatus::kPaused:
-      node_data->SetDescription(
+      app_status_description =
           ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
-              IDS_APP_LIST_PAUSED_APP));
+              IDS_APP_LIST_PAUSED_APP);
       break;
     default:
       if (item_weak_->is_new_install()) {
-        node_data->SetDescription(
+        app_status_description =
             ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
-                IDS_APP_LIST_NEW_INSTALL_ACCESSIBILE_DESCRIPTION));
+                IDS_APP_LIST_NEW_INSTALL_ACCESSIBILE_DESCRIPTION);
       }
       break;
   }
+  if (!app_status_description.empty()) {
+    descriptions.push_back(app_status_description);
+  }
+
+  // Set the concatenated descriptions.
+  if (!descriptions.empty()) {
+    node_data->SetDescription(base::JoinString(descriptions, u" "));
+  }
 }
 
 void AppListItemView::OnContextMenuModelReceived(
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 0776bfec..175f5e7 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -3983,27 +3983,12 @@
       <message name="IDS_AMBIENT_ACCELERATOR_CYCLE_BACKWARD_MRU" translateable="false" desc="Action for accelerator action - Open the window that has been unused for the longest time.">
         Press and hold <ph name="MODIFIER_1">$1<ex>alt</ex></ph><ph name="MODIFIER_2">$2<ex>shift</ex></ph>, tap <ph name="KEY">$3<ex>tab</ex></ph> until you get to the window you want to open, then release
       </message>
-      <message name="IDS_AMBIENT_ACCELERATOR_FIND_NEXT" translateable="false" desc="Action for accelerator action - Go to the next match for your search.">
-        <ph name="MODIFIER">$1<ex>ctrl</ex></ph><ph name="KEY_ONE">$2<ex>g</ex></ph> or <ph name="KEY_TWO">$3<ex>enter</ex></ph>
-      </message>
-      <message name="IDS_AMBIENT_ACCELERATOR_FIND_PREVIOUS" translateable="false" desc="Action for accelerator action - Go to the previous match for your search.">
-        <ph name="MODIFIER_ONE">$1<ex>ctrl</ex></ph><ph name="MODIFIER_TWO">$2<ex>shift</ex></ph><ph name="KEY_ONE">$3<ex>g</ex></ph> or <ph name="MODIFIER_THREE">$4<ex>shift</ex></ph><ph name="KEY_TWO">$5<ex>enter</ex></ph>
-      </message>
-      <message name="IDS_AMBIENT_ACCELERATOR_FOCUS_SEARCH" translateable="false" desc="Action for accelerator action - Place focus in search address bar.">
-        <ph name="MODIFIER">$1<ex>ctrl</ex></ph><ph name="KEY_ONE">$2<ex>k</ex></ph> or <ph name="KEY_TWO">$3<ex>e</ex></ph>
-      </message>
-      <message name="IDS_AMBIENT_ACCELERATOR_RELOAD" translateable="false" desc="Action for accelerator action - Reload your current page.">
-        <ph name="KEY_ONE">$1<ex>browser_refresh</ex></ph> or <ph name="MODIFIER">$2<ex>ctrl</ex></ph><ph name="KEY_TWO">$3<ex>r</ex></ph>
-      </message>
       <message name="IDS_AMBIENT_ACCELERATOR_RIGHT_CLICK" translateable="false" desc="Action for accelerator action - Right-click a link.">
         Press <ph name="MODIFIER">$1<ex>alt</ex></ph> and click a link
       </message>
       <message name="IDS_AMBIENT_ACCELERATOR_SAVE_LINK_AS_BOOKMARK" translateable="false" desc="Action for accelerator action - Drag link to bookmarks bar.">
         Drag link to bookmarks bar
       </message>
-      <message name="IDS_AMBIENT_ACCELERATOR_SHOW_APP_MENU" translateable="false" desc="Action for accelerator action - Show app menu.">
-        <ph name="MODIFIER">$1<ex>alt</ex></ph><ph name="KEY_ONE">$2<ex>e</ex></ph> or <ph name="KEY_TWO">$3<ex>f</ex></ph>
-      </message>
       <message name="IDS_AMBIENT_ACCELERATOR_LAUNCH_NUMBERED_APP" translateable="false" desc="Action for accelerator action - Click icons 1-8 on your shelf.">
         <ph name="MODIFIER">$1<ex>alt</ex></ph> <ph name="DELIMITER">$2<ex>+</ex></ph> <ph name="KEY_ONE">$3<ex>1</ex></ph> through <ph name="KEY_TWO">$4<ex>8</ex></ph>
       </message>
@@ -6039,6 +6024,11 @@
       <message name="IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME" desc="The spoken feedback text for navigating to a folder button in top level app list">
         Folder <ph name="folder_name">$1<ex>OEM Apps</ex></ph>
       </message>
+      <message name="IDS_APP_LIST_FOLDER_NUMBER_OF_APPS_ACCESSIBILE_DESCRIPTION" desc="The spoken feedback text that announces the number of apps in a folder when navigating to the folder button in top level app list">
+        {NUM_APPS, plural,
+         =1 {1 app}
+         other {# apps}}
+      </message>
       <message name="IDS_APP_LIST_FOLDER_CLOSE_FOLDER_ACCESSIBILE_NAME" desc="The spoken feedback text for closing an app launcher folder">
         Close folder
       </message>
diff --git a/ash/ash_strings_grd/IDS_APP_LIST_FOLDER_NUMBER_OF_APPS_ACCESSIBILE_DESCRIPTION.png.sha1 b/ash/ash_strings_grd/IDS_APP_LIST_FOLDER_NUMBER_OF_APPS_ACCESSIBILE_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..6b34ff28
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_APP_LIST_FOLDER_NUMBER_OF_APPS_ACCESSIBILE_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+eba145d904aa0b997a6a49ef2cc6824b262a6a2e
\ No newline at end of file
diff --git a/ash/clipboard/clipboard_history_controller_impl.cc b/ash/clipboard/clipboard_history_controller_impl.cc
index b5f53a0..10b8c69d 100644
--- a/ash/clipboard/clipboard_history_controller_impl.cc
+++ b/ash/clipboard/clipboard_history_controller_impl.cc
@@ -276,7 +276,7 @@
 
 void ClipboardHistoryControllerImpl::Shutdown() {
   if (IsMenuShowing()) {
-    context_menu_->Cancel();
+    context_menu_->Cancel(/*will_paste_item=*/false);
   }
   nudge_controller_.reset();
 }
@@ -326,6 +326,14 @@
     const gfx::Rect& anchor_rect,
     ui::MenuSourceType source_type,
     crosapi::mojom::ClipboardHistoryControllerShowSource show_source) {
+  ShowMenu(anchor_rect, source_type, show_source, OnMenuClosingCallback());
+}
+
+void ClipboardHistoryControllerImpl::ShowMenu(
+    const gfx::Rect& anchor_rect,
+    ui::MenuSourceType source_type,
+    crosapi::mojom::ClipboardHistoryControllerShowSource show_source,
+    OnMenuClosingCallback callback) {
   if (IsMenuShowing() || !CanShowMenu())
     return;
 
@@ -335,8 +343,9 @@
   if (active_menu_instance)
     active_menu_instance->Cancel(views::MenuController::ExitType::kAll);
 
+  // `Unretained()` is safe because `this` owns `context_menu_`.
   context_menu_ = ClipboardHistoryMenuModelAdapter::Create(
-      menu_delegate_.get(),
+      menu_delegate_.get(), std::move(callback),
       base::BindRepeating(&ClipboardHistoryControllerImpl::OnMenuClosed,
                           base::Unretained(this)),
       clipboard_history_.get(), resource_manager_.get());
@@ -570,7 +579,7 @@
   weak_ptr_factory_.InvalidateWeakPtrs();
   if (!IsMenuShowing())
     return;
-  context_menu_->Cancel();
+  context_menu_->Cancel(/*will_paste_item=*/false);
 }
 
 void ClipboardHistoryControllerImpl::OnOperationConfirmed(bool copy) {
@@ -721,12 +730,18 @@
 
   // Deactivate ClipboardImageModelFactory prior to pasting to ensure that any
   // modifications to the clipboard for HTML rendering purposes are reversed.
-  ClipboardImageModelFactory::Get()->Deactivate();
+  // This factory may be nullptr in tests.
+  if (auto* clipboard_image_factory = ClipboardImageModelFactory::Get()) {
+    clipboard_image_factory->Deactivate();
+  }
 
   // Force close the context menu. Failure to do so before dispatching our
   // synthetic key event will result in the context menu consuming the event.
+  // When closing the menu, indicate that the menu is closing because of an
+  // imminent paste. Note that in some cases, this will indicate paste intent
+  // for pastes that ultimately fail. For now, this is an acceptable inaccuracy.
   DCHECK(context_menu_);
-  context_menu_->Cancel();
+  context_menu_->Cancel(/*will_paste_item=*/true);
 
   auto* active_window = window_util::GetActiveWindow();
   if (!active_window)
@@ -920,7 +935,7 @@
 
   // If the item to be deleted is the last one, close the whole menu.
   if (context_menu_->GetMenuItemsCount() == 1) {
-    context_menu_->Cancel();
+    context_menu_->Cancel(/*will_paste_item=*/false);
     return;
   }
 
diff --git a/ash/clipboard/clipboard_history_controller_impl.h b/ash/clipboard/clipboard_history_controller_impl.h
index a38bd6f..264efd3 100644
--- a/ash/clipboard/clipboard_history_controller_impl.h
+++ b/ash/clipboard/clipboard_history_controller_impl.h
@@ -95,6 +95,11 @@
                 ui::MenuSourceType source_type,
                 crosapi::mojom::ClipboardHistoryControllerShowSource
                     show_source) override;
+  void ShowMenu(
+      const gfx::Rect& anchor_rect,
+      ui::MenuSourceType source_type,
+      crosapi::mojom::ClipboardHistoryControllerShowSource show_source,
+      OnMenuClosingCallback callback) override;
   void GetHistoryValues(GetHistoryValuesCallback callback) const override;
 
   // Returns bounds for the contextual menu in screen coordinates.
diff --git a/ash/clipboard/clipboard_history_controller_unittest.cc b/ash/clipboard/clipboard_history_controller_unittest.cc
index 494649a0..bf14d50c 100644
--- a/ash/clipboard/clipboard_history_controller_unittest.cc
+++ b/ash/clipboard/clipboard_history_controller_unittest.cc
@@ -182,7 +182,7 @@
 }
 
 // Tests that search + v shows a menu when there is something to show.
-TEST_F(ClipboardHistoryControllerTest, MultiShowMenu) {
+TEST_F(ClipboardHistoryControllerTest, ShowMenu) {
   base::HistogramTester histogram_tester;
   // Copy something to enable the clipboard history menu.
   WriteTextToClipboardAndConfirm(u"test");
@@ -235,6 +235,43 @@
       "Ash.ClipboardHistory.ContextMenu.UserJourneyTime", 2);
 }
 
+// Tests that the client-provided `OnMenuClosingCallback` runs before the menu
+// closes.
+TEST_F(ClipboardHistoryControllerTest, OnMenuClosingCallback) {
+  base::test::RepeatingTestFuture<bool> on_menu_closing_future;
+  // Copy something to enable the clipboard history menu.
+  WriteTextToClipboardAndConfirm(u"test");
+
+  gfx::Rect test_window_rect(100, 100, 100, 100);
+  std::unique_ptr<aura::Window> window(CreateTestWindow(test_window_rect));
+
+  // Show the menu with an `OnMenuClosingCallback`.
+  GetClipboardHistoryController()->ShowMenu(
+      test_window_rect, ui::MenuSourceType::MENU_SOURCE_NONE,
+      crosapi::mojom::ClipboardHistoryControllerShowSource::kUnknown,
+      on_menu_closing_future.GetCallback());
+  EXPECT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
+  EXPECT_TRUE(on_menu_closing_future.IsEmpty());
+
+  // Hide the menu. The callback should indicate that nothing will be pasted.
+  PressAndReleaseKey(ui::VKEY_ESCAPE);
+  EXPECT_FALSE(GetClipboardHistoryController()->IsMenuShowing());
+  EXPECT_FALSE(on_menu_closing_future.Take());
+
+  // Show the menu again.
+  GetClipboardHistoryController()->ShowMenu(
+      test_window_rect, ui::MenuSourceType::MENU_SOURCE_NONE,
+      crosapi::mojom::ClipboardHistoryControllerShowSource::kUnknown,
+      on_menu_closing_future.GetCallback());
+  EXPECT_TRUE(GetClipboardHistoryController()->IsMenuShowing());
+  EXPECT_TRUE(on_menu_closing_future.IsEmpty());
+
+  // Toggle the menu closed. The callback should indicate a pending paste.
+  PressAndReleaseKey(ui::VKEY_V, ui::EF_COMMAND_DOWN);
+  EXPECT_FALSE(GetClipboardHistoryController()->IsMenuShowing());
+  EXPECT_TRUE(on_menu_closing_future.Take());
+}
+
 // Verifies that the clipboard history is disabled in some user modes, which
 // means that the clipboard history should not be recorded and meanwhile the
 // menu view should not show (https://crbug.com/1100739).
diff --git a/ash/clipboard/clipboard_history_menu_model_adapter.cc b/ash/clipboard/clipboard_history_menu_model_adapter.cc
index 23b0b0d3..a56b787 100644
--- a/ash/clipboard/clipboard_history_menu_model_adapter.cc
+++ b/ash/clipboard/clipboard_history_menu_model_adapter.cc
@@ -7,6 +7,7 @@
 #include "ash/clipboard/clipboard_history.h"
 #include "ash/clipboard/clipboard_history_util.h"
 #include "ash/clipboard/views/clipboard_history_item_view.h"
+#include "ash/public/cpp/clipboard_history_controller.h"
 #include "ash/public/cpp/clipboard_image_model_factory.h"
 #include "ash/wm/window_util.h"
 #include "base/functional/bind.h"
@@ -15,6 +16,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/simple_menu_model.h"
 #include "ui/base/ui_base_types.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/strings/grit/ui_strings.h"
@@ -27,6 +29,36 @@
 
 namespace ash {
 
+// ClipboardHistoryMenuModelAdapter::MenuModelWithWillCloseCallback ------------
+
+// Utility class that allows `ClipboardHistoryMenuModelAdapter` to run a task
+// before its menu closes.
+class ClipboardHistoryMenuModelAdapter::MenuModelWithWillCloseCallback
+    : public ui::SimpleMenuModel {
+ public:
+  MenuModelWithWillCloseCallback(
+      ui::SimpleMenuModel::Delegate* delegate,
+      ClipboardHistoryController::OnMenuClosingCallback callback)
+      : ui::SimpleMenuModel(delegate), callback_(std::move(callback)) {}
+
+  // ui::SimpleMenuModel:
+  void MenuWillClose() override {
+    if (callback_) {
+      std::move(callback_).Run(will_paste_item_);
+    }
+
+    ui::SimpleMenuModel::MenuWillClose();
+  }
+
+  void set_will_paste_item(bool will_paste_item) {
+    will_paste_item_ = will_paste_item;
+  }
+
+ private:
+  ClipboardHistoryController::OnMenuClosingCallback callback_;
+  bool will_paste_item_ = false;
+};
+
 // ClipboardHistoryMenuModelAdapter::ScopedA11yIgnore --------------------------
 
 // The scoped class to disable a11y for all items views.
@@ -57,11 +89,13 @@
 std::unique_ptr<ClipboardHistoryMenuModelAdapter>
 ClipboardHistoryMenuModelAdapter::Create(
     ui::SimpleMenuModel::Delegate* delegate,
+    ClipboardHistoryController::OnMenuClosingCallback on_menu_closing_callback,
     base::RepeatingClosure menu_closed_callback,
     const ClipboardHistory* clipboard_history,
     const ClipboardHistoryResourceManager* resource_manager) {
   return base::WrapUnique(new ClipboardHistoryMenuModelAdapter(
-      std::make_unique<ui::SimpleMenuModel>(delegate),
+      std::make_unique<MenuModelWithWillCloseCallback>(
+          delegate, std::move(on_menu_closing_callback)),
       std::move(menu_closed_callback), clipboard_history, resource_manager));
 }
 
@@ -97,7 +131,10 @@
   }
 
   // Start async rendering of HTML, if any exists.
-  ClipboardImageModelFactory::Get()->Activate();
+  // This factory may be nullptr in tests.
+  if (auto* clipboard_image_factory = ClipboardImageModelFactory::Get()) {
+    clipboard_image_factory->Activate();
+  }
 
   root_view_ = CreateMenu();
   root_view_->SetTitle(
@@ -115,7 +152,8 @@
   return menu_runner_ && menu_runner_->IsRunning();
 }
 
-void ClipboardHistoryMenuModelAdapter::Cancel() {
+void ClipboardHistoryMenuModelAdapter::Cancel(bool will_paste_item) {
+  model_->set_will_paste_item(will_paste_item);
   DCHECK(menu_runner_);
   menu_runner_->Cancel();
 }
@@ -283,7 +321,7 @@
 }
 
 ClipboardHistoryMenuModelAdapter::ClipboardHistoryMenuModelAdapter(
-    std::unique_ptr<ui::SimpleMenuModel> model,
+    std::unique_ptr<MenuModelWithWillCloseCallback> model,
     base::RepeatingClosure menu_closed_callback,
     const ClipboardHistory* clipboard_history,
     const ClipboardHistoryResourceManager* resource_manager)
@@ -412,7 +450,10 @@
   // Because when hitting here, this instance is going to be destructed soon.
   weak_ptr_factory_.InvalidateWeakPtrs();
 
-  ClipboardImageModelFactory::Get()->Deactivate();
+  // This factory may be nullptr in tests.
+  if (auto* clipboard_image_factory = ClipboardImageModelFactory::Get()) {
+    clipboard_image_factory->Deactivate();
+  }
   const base::TimeDelta user_journey_time =
       base::TimeTicks::Now() - menu_open_time_;
   UMA_HISTOGRAM_TIMES("Ash.ClipboardHistory.ContextMenu.UserJourneyTime",
diff --git a/ash/clipboard/clipboard_history_menu_model_adapter.h b/ash/clipboard/clipboard_history_menu_model_adapter.h
index d7f086f4..3c43346 100644
--- a/ash/clipboard/clipboard_history_menu_model_adapter.h
+++ b/ash/clipboard/clipboard_history_menu_model_adapter.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "ash/ash_export.h"
+#include "ash/public/cpp/clipboard_history_controller.h"
 #include "base/time/time.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/models/simple_menu_model.h"
@@ -40,6 +41,8 @@
  public:
   static std::unique_ptr<ClipboardHistoryMenuModelAdapter> Create(
       ui::SimpleMenuModel::Delegate* delegate,
+      ClipboardHistoryController::OnMenuClosingCallback
+          on_menu_closing_callback,
       base::RepeatingClosure menu_closed_callback,
       const ClipboardHistory* clipboard_history,
       const ClipboardHistoryResourceManager* resource_manager);
@@ -58,8 +61,9 @@
   // Returns if the menu is currently running.
   bool IsRunning() const;
 
-  // Hides and cancels the menu.
-  void Cancel();
+  // Hides and cancels the menu. `will_paste_item` indicates whether a clipboard
+  // history item will be pasted after the menu is closed.
+  void Cancel(bool will_paste_item);
 
   // Returns the command of the currently selected menu item. If no menu item is
   // currently selected, returns |absl::nullopt|.
@@ -93,12 +97,13 @@
   views::MenuItemView* GetMenuItemViewAtForTest(size_t index);
 
  private:
+  class MenuModelWithWillCloseCallback;
   class ScopedA11yIgnore;
 
   using ItemViewsByCommandId = std::map<int, ClipboardHistoryItemView*>;
 
   ClipboardHistoryMenuModelAdapter(
-      std::unique_ptr<ui::SimpleMenuModel> model,
+      std::unique_ptr<MenuModelWithWillCloseCallback> model,
       base::RepeatingClosure menu_closed_callback,
       const ClipboardHistory* clipboard_history,
       const ClipboardHistoryResourceManager* resource_manager);
@@ -121,7 +126,7 @@
   void OnMenuClosed(views::MenuItemView* menu) override;
 
   // The model which holds the contents of the menu.
-  std::unique_ptr<ui::SimpleMenuModel> const model_;
+  std::unique_ptr<MenuModelWithWillCloseCallback> const model_;
   // The root MenuItemView which contains all child MenuItemViews. Owned by
   // |menu_runner_|.
   views::MenuItemView* root_view_ = nullptr;
diff --git a/ash/components/arc/mojom/notifications.mojom b/ash/components/arc/mojom/notifications.mojom
index df860b5..d18315e 100644
--- a/ash/components/arc/mojom/notifications.mojom
+++ b/ash/components/arc/mojom/notifications.mojom
@@ -141,10 +141,10 @@
   // DEPRECATED: Image for image notifications.
   [MinVersion=3]
   ArcBitmap? deprecated_big_picture;
-  // DEPRECATED: Flag if a notification is a custom notification backed by a
+  // Flag if a notification is a custom notification backed by a
   // notification surface.
   [MinVersion=5]
-  bool deprecated_use_custom_notification;
+  bool is_custom_notification;
   [MinVersion=6]
   ArcBitmap? small_icon;
   // A snapshot image to show before the notification window is created.
@@ -188,8 +188,9 @@
   // available for the user to trigger remote input.
   [MinVersion=27]
   bool is_inline_reply_enabled;
+  // True if rendering should be done on the Chrome side
   [MinVersion=28]
-  bool is_custom_notification;
+  bool render_on_chrome;
 };
 
 struct ArcDoNotDisturbStatus {
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index cf5badb..2fe2690 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -497,6 +497,10 @@
              "CryptohomeRecovery",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Adds a desk button to the shelf that the user can use to navigate between
+// desks.
+BASE_FEATURE(kDeskButton, "DeskButton", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables Sync for desk templates on ChromeOS.
 BASE_FEATURE(kDeskTemplateSync,
              "DeskTemplateSync",
@@ -1067,12 +1071,6 @@
              "HandwritingLegacyRecognition",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables new on-device recognition for legacy handwriting input in all
-// supported languages.
-BASE_FEATURE(kHandwritingLegacyRecognitionAllLang,
-             "HandwritingLegacyRecognitionAllLang",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Enables downloading the handwriting libraries via DLC.
 BASE_FEATURE(kHandwritingLibraryDlc,
              "HandwritingLibraryDlc",
@@ -1424,6 +1422,18 @@
              "EcheNetworkConnectionState",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Time limit before re-attempting a background connection to check if the
+// network is suitable.
+const base::FeatureParam<base::TimeDelta>
+    kEcheBackgroundConnectionAttemptThrottleTimeout{
+        &kEcheNetworkConnectionState,
+        "EcheBackgroundConnectionAttemptThrottleTimeout", base::Seconds(10)};
+
+// Time limit before requiring a new connection check to show apps UI.
+const base::FeatureParam<base::TimeDelta> kEcheConnectionStatusResetTimeout{
+    &kEcheNetworkConnectionState, "EcheConnectionStatusResetTimeout",
+    base::Minutes(10)};
+
 // Enables multi-zone rgb keyboard customization.
 BASE_FEATURE(kMultiZoneRgbKeyboard,
              "MultiZoneRgbKeyboard",
@@ -2519,6 +2529,10 @@
   return chromeos::features::IsDarkLightModeEnabled();
 }
 
+bool IsDeskButtonEnabled() {
+  return base::FeatureList::IsEnabled(kDeskButton);
+}
+
 bool IsDeskTemplateSyncEnabled() {
   return base::FeatureList::IsEnabled(kDeskTemplateSync);
 }
@@ -2805,8 +2819,7 @@
 }
 
 bool IsLanguagePacksEnabled() {
-  return base::FeatureList::IsEnabled(kHandwritingLegacyRecognition) ||
-         base::FeatureList::IsEnabled(kHandwritingLegacyRecognitionAllLang);
+  return base::FeatureList::IsEnabled(kHandwritingLegacyRecognition);
 }
 
 bool IsLauncherNudgeShortIntervalEnabled() {
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 621bc56..56758b5 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -139,6 +139,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCryptAuthV2DeviceSync);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCryptAuthV2Enrollment);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kCryptohomeRecovery);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kDeskButton);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kDeskTemplateSync);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kDeviceActiveClient);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -302,6 +303,12 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kEcheNetworkConnectionState);
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::FeatureParam<base::TimeDelta>
+    kEcheBackgroundConnectionAttemptThrottleTimeout;
+COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::FeatureParam<base::TimeDelta>
+    kEcheConnectionStatusResetTimeout;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kForceEnableServerSideSpeechRecognitionForDev);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kForceReSyncDrive);
 COMPONENT_EXPORT(ASH_CONSTANTS)
@@ -332,8 +339,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kProductivityLauncherImageSearch);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kLauncherItemColorSync);
-COMPONENT_EXPORT(ASH_CONSTANTS)
-BASE_DECLARE_FEATURE(kHandwritingLegacyRecognitionAllLang);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kHandwritingLibraryDlc);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kHelpAppDiscoverTabNotificationAllChannels);
@@ -689,6 +694,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCryptohomeRecoveryEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDarkLightModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDeepLinkingEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDeskButtonEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDeskTemplateSyncEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsInputDeviceSettingsSplitEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
diff --git a/ash/events/keyboard_capability_unittest.cc b/ash/events/keyboard_capability_unittest.cc
index 006b263..3e18c69 100644
--- a/ash/events/keyboard_capability_unittest.cc
+++ b/ash/events/keyboard_capability_unittest.cc
@@ -34,6 +34,27 @@
 constexpr int kDeviceId1 = 5;
 constexpr int kDeviceId2 = 10;
 
+ui::InputDeviceType INTERNAL = ui::InputDeviceType::INPUT_DEVICE_INTERNAL;
+ui::InputDeviceType EXTERNAL_USB = ui::InputDeviceType::INPUT_DEVICE_USB;
+ui::InputDeviceType EXTERNAL_BLUETOOTH =
+    ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH;
+// For INPUT_DEVICE_UNKNOWN type, we treat it as external keyboard.
+ui::InputDeviceType EXTERNAL_UNKNOWN =
+    ui::InputDeviceType::INPUT_DEVICE_UNKNOWN;
+
+struct KeyEventTestData {
+  // All currently connected keyboards' connection type, e.g.
+  // INPUT_DEVICE_INTERNAL.
+  std::vector<ui::InputDeviceType> keyboard_connection_types;
+  // All currently connected keyboards' layout types.
+  std::vector<std::string> keyboard_layout_types;
+  ui::KeyboardCode key_code;
+  // Expected result of whether this key event exists on each keyboard.
+  std::vector<bool> expected_has_key_event;
+  // Expected result of whether this key event exists on all connected.
+  bool expected_has_key_event_on_any_keyboard;
+};
+
 // NOTE: This only creates a simple ui::InputDevice based on a device
 // capabilities report; it is not suitable for subclasses of ui::InputDevice.
 ui::InputDevice InputDeviceFromCapabilities(
@@ -75,6 +96,11 @@
                              std::move(sysfs_properties));
   }
 
+  void RemoveAllDevices() {
+    fake_udev_.Reset();
+    fake_keyboard_devices_.clear();
+  }
+
  private:
   testing::FakeUdevLoader fake_udev_;
   std::vector<ui::InputDevice> fake_keyboard_devices_;
@@ -329,6 +355,21 @@
   ASSERT_EQ(0u, keyboard_capability_->keyboard_info_map().size());
 }
 
+TEST_F(KeyboardCapabilityTest, TestIsTopRowKey) {
+  for (const auto& [key_code, _] : ui::kLayout1TopRowKeyToFKeyMap) {
+    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
+  }
+  for (const auto& [key_code, _] : ui::kLayout2TopRowKeyToFKeyMap) {
+    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
+  }
+  for (const auto& [key_code, _] : ui::kLayoutWilcoDrallionTopRowKeyToFKeyMap) {
+    EXPECT_TRUE(keyboard_capability_->IsTopRowKey(key_code));
+  }
+
+  // A key not in any of the above maps is not a top row key.
+  EXPECT_FALSE(keyboard_capability_->IsTopRowKey(ui::KeyboardCode::VKEY_A));
+}
+
 class ModifierKeyTest : public KeyboardCapabilityTest,
                         public testing::WithParamInterface<
                             std::tuple<ui::DeviceCapabilities,
@@ -384,4 +425,113 @@
   EXPECT_EQ(expected_modifier_keys, modifier_keys);
 }
 
+class KeyEventTest : public KeyboardCapabilityTest,
+                     public testing::WithParamInterface<KeyEventTestData> {};
+
+// Tests that given the keyboard connection type and layout type, check if this
+// keyboard has a specific key event.
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    KeyEventTest,
+    testing::ValuesIn(std::vector<KeyEventTestData>{
+        // Testing top row keys.
+        {{INTERNAL},
+         {kKbdTopRowLayout1Tag},
+         ui::VKEY_BROWSER_FORWARD,
+         {true},
+         true},
+        {{EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout1Tag},
+         ui::VKEY_ZOOM,
+         {true},
+         true},
+        {{EXTERNAL_USB},
+         {kKbdTopRowLayout1Tag},
+         ui::VKEY_MEDIA_PLAY_PAUSE,
+         {false},
+         false},
+        {{INTERNAL},
+         {kKbdTopRowLayout2Tag},
+         ui::VKEY_BROWSER_FORWARD,
+         {false},
+         false},
+        {{EXTERNAL_UNKNOWN},
+         {kKbdTopRowLayout2Tag},
+         ui::VKEY_MEDIA_PLAY_PAUSE,
+         {true},
+         true},
+        {{INTERNAL}, {kKbdTopRowLayoutWilcoTag}, ui::VKEY_ZOOM, {true}, true},
+        {{EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayoutDrallionTag},
+         ui::VKEY_BRIGHTNESS_UP,
+         {true},
+         true},
+        {{INTERNAL, EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayout2Tag},
+         ui::VKEY_BROWSER_FORWARD,
+         {true, false},
+         true},
+        {{INTERNAL, EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout2Tag, kKbdTopRowLayout2Tag},
+         ui::VKEY_BROWSER_FORWARD,
+         {false, false},
+         false},
+        {{INTERNAL, EXTERNAL_USB, EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayout2Tag, kKbdTopRowLayoutWilcoTag},
+         ui::VKEY_VOLUME_UP,
+         {true, true, true},
+         true},
+
+        // Testing six pack keys.
+        {{INTERNAL}, {kKbdTopRowLayout1Tag}, ui::VKEY_INSERT, {false}, false},
+        {{EXTERNAL_USB}, {kKbdTopRowLayout1Tag}, ui::VKEY_INSERT, {true}, true},
+        {{INTERNAL, EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayoutWilcoTag},
+         ui::VKEY_HOME,
+         {false, true},
+         true},
+
+        // Testing other keys.
+        {{INTERNAL}, {kKbdTopRowLayout1Tag}, ui::VKEY_LEFT, {true}, true},
+        {{EXTERNAL_BLUETOOTH},
+         {kKbdTopRowLayout2Tag},
+         ui::VKEY_ESCAPE,
+         {true},
+         true},
+        {{EXTERNAL_UNKNOWN},
+         {kKbdTopRowLayoutWilcoTag},
+         ui::VKEY_A,
+         {true},
+         true},
+        {{INTERNAL}, {kKbdTopRowLayoutDrallionTag}, ui::VKEY_2, {true}, true},
+    }));
+
+TEST_P(KeyEventTest, TestHasKeyEvent) {
+  auto [keyboard_connection_types, keyboard_layout_types, key_code,
+        expected_has_key_event, expected_has_key_event_on_any_keyboard] =
+      GetParam();
+
+  fake_keyboard_manager_->RemoveAllDevices();
+  for (size_t i = 0; i < keyboard_layout_types.size(); i++) {
+    std::string layout = keyboard_layout_types[i];
+    ui::InputDevice fake_keyboard(
+        /*id=*/i, /*type=*/keyboard_connection_types[i],
+        /*name=*/layout);
+    fake_keyboard.sys_path = base::FilePath("path" + layout);
+    fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, layout);
+
+    if (expected_has_key_event[i]) {
+      EXPECT_TRUE(keyboard_capability_->HasKeyEvent(key_code, fake_keyboard));
+    } else {
+      EXPECT_FALSE(keyboard_capability_->HasKeyEvent(key_code, fake_keyboard));
+    }
+  }
+
+  if (expected_has_key_event_on_any_keyboard) {
+    EXPECT_TRUE(keyboard_capability_->HasKeyEventOnAnyKeyboard(key_code));
+  } else {
+    EXPECT_FALSE(keyboard_capability_->HasKeyEventOnAnyKeyboard(key_code));
+  }
+}
+
 }  // namespace ash
diff --git a/ash/public/cpp/clipboard_history_controller.h b/ash/public/cpp/clipboard_history_controller.h
index f159ab1..e204f65a 100644
--- a/ash/public/cpp/clipboard_history_controller.h
+++ b/ash/public/cpp/clipboard_history_controller.h
@@ -28,6 +28,7 @@
  public:
   using GetHistoryValuesCallback =
       base::OnceCallback<void(std::vector<ClipboardHistoryItem>)>;
+  using OnMenuClosingCallback = base::OnceCallback<void(bool will_paste_item)>;
 
   class Observer : public base::CheckedObserver {
    public:
@@ -51,11 +52,20 @@
   virtual bool CanShowMenu() const = 0;
 
   // Shows the clipboard history menu triggered by `source_type` at the
-  // specified position.
+  // position specified by `anchor_rect`, provided the menu can currently be
+  // shown. `show_source` indicates how the user opened the menu. As long as the
+  // menu is shown, `callback` runs just before the menu closes to indicate
+  // whether a clipboard history paste is imminent.
+  // TODO(b/267694199): Make these functions return whether the menu was shown.
   virtual void ShowMenu(
       const gfx::Rect& anchor_rect,
       ui::MenuSourceType source_type,
       crosapi::mojom::ClipboardHistoryControllerShowSource show_source) = 0;
+  virtual void ShowMenu(
+      const gfx::Rect& anchor_rect,
+      ui::MenuSourceType source_type,
+      crosapi::mojom::ClipboardHistoryControllerShowSource show_source,
+      OnMenuClosingCallback callback) = 0;
 
   // Notify the clipboard history that a screenshot notification was created.
   virtual void OnScreenshotNotificationCreated() = 0;
diff --git a/ash/public/cpp/holding_space/holding_space_client.h b/ash/public/cpp/holding_space/holding_space_client.h
index 4574557..25928ed 100644
--- a/ash/public/cpp/holding_space/holding_space_client.h
+++ b/ash/public/cpp/holding_space/holding_space_client.h
@@ -24,9 +24,17 @@
  public:
   using SuccessCallback = base::OnceCallback<void(bool)>;
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a diagnostics log item backed by the provided `file_path`.
   virtual void AddDiagnosticsLog(const base::FilePath& file_path) = 0;
 
+  // Adds an item of the specified `type` backed by the specified `file_path`.
+  // Returns the id of the added item or an empty string if the item was not
+  // added due to de-duplication checks.
+  virtual const std::string& AddItemOfType(HoldingSpaceItem::Type type,
+                                           const base::FilePath& file_path) = 0;
+
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a screen capture item backed by the provided `file_path`.
   // NOTE: `type` must refer to a screen capture type.
   virtual void AddScreenCapture(HoldingSpaceItem::Type type,
diff --git a/ash/public/cpp/holding_space/holding_space_item.cc b/ash/public/cpp/holding_space/holding_space_item.cc
index 21ea462..a3751a12 100644
--- a/ash/public/cpp/holding_space/holding_space_item.cc
+++ b/ash/public/cpp/holding_space/holding_space_item.cc
@@ -115,6 +115,11 @@
     case Type::kDownload:
     case Type::kLacrosDownload:
       return true;
+    case Type::kCameraAppPhoto:
+    case Type::kCameraAppScanJpg:
+    case Type::kCameraAppScanPdf:
+    case Type::kCameraAppVideoGif:
+    case Type::kCameraAppVideoMp4:
     case Type::kDiagnosticsLog:
     case Type::kDriveSuggestion:
     case Type::kLocalSuggestion:
@@ -138,6 +143,11 @@
     case Type::kScreenshot:
       return true;
     case Type::kArcDownload:
+    case Type::kCameraAppPhoto:
+    case Type::kCameraAppScanJpg:
+    case Type::kCameraAppScanPdf:
+    case Type::kCameraAppVideoGif:
+    case Type::kCameraAppVideoMp4:
     case Type::kDiagnosticsLog:
     case Type::kDownload:
     case Type::kDriveSuggestion:
@@ -159,6 +169,11 @@
     case Type::kLocalSuggestion:
       return true;
     case Type::kArcDownload:
+    case Type::kCameraAppPhoto:
+    case Type::kCameraAppScanJpg:
+    case Type::kCameraAppScanPdf:
+    case Type::kCameraAppVideoGif:
+    case Type::kCameraAppVideoMp4:
     case Type::kDiagnosticsLog:
     case Type::kDownload:
     case Type::kLacrosDownload:
diff --git a/ash/public/cpp/holding_space/holding_space_item.h b/ash/public/cpp/holding_space/holding_space_item.h
index b566e536..175c71b 100644
--- a/ash/public/cpp/holding_space/holding_space_item.h
+++ b/ash/public/cpp/holding_space/holding_space_item.h
@@ -86,7 +86,12 @@
     kDriveSuggestion = 11,
     kLocalSuggestion = 12,
     kScreenRecordingGif = 13,
-    kMaxValue = kScreenRecordingGif,
+    kCameraAppPhoto = 14,
+    kCameraAppScanJpg = 15,
+    kCameraAppScanPdf = 16,
+    kCameraAppVideoGif = 17,
+    kCameraAppVideoMp4 = 18,
+    kMaxValue = kCameraAppVideoMp4,
   };
 
   HoldingSpaceItem(const HoldingSpaceItem&) = delete;
diff --git a/ash/public/cpp/holding_space/holding_space_item_unittest.cc b/ash/public/cpp/holding_space/holding_space_item_unittest.cc
index e9c2e4e..2e8faf7 100644
--- a/ash/public/cpp/holding_space/holding_space_item_unittest.cc
+++ b/ash/public/cpp/holding_space/holding_space_item_unittest.cc
@@ -165,6 +165,11 @@
       EXPECT_TRUE(HoldingSpaceItem::IsScreenCapture(type));
       return;
     case HoldingSpaceItem::Type::kArcDownload:
+    case HoldingSpaceItem::Type::kCameraAppPhoto:
+    case HoldingSpaceItem::Type::kCameraAppScanJpg:
+    case HoldingSpaceItem::Type::kCameraAppScanPdf:
+    case HoldingSpaceItem::Type::kCameraAppVideoGif:
+    case HoldingSpaceItem::Type::kCameraAppVideoMp4:
     case HoldingSpaceItem::Type::kDiagnosticsLog:
     case HoldingSpaceItem::Type::kDownload:
     case HoldingSpaceItem::Type::kDriveSuggestion:
diff --git a/ash/public/cpp/holding_space/holding_space_model.cc b/ash/public/cpp/holding_space/holding_space_model.cc
index 6a26e16..e34f31f 100644
--- a/ash/public/cpp/holding_space/holding_space_model.cc
+++ b/ash/public/cpp/holding_space/holding_space_model.cc
@@ -204,6 +204,15 @@
       [](std::map<HoldingSpaceSectionId, size_t>& item_counts_per_section_id,
          const HoldingSpaceItem* item) {
         const auto* section = GetHoldingSpaceSection(item->type());
+        if (!section) {
+          // TODO(http://b/274484210): Update views for camera app types.
+          DCHECK(item->type() == HoldingSpaceItem::Type::kCameraAppPhoto ||
+                 item->type() == HoldingSpaceItem::Type::kCameraAppScanJpg ||
+                 item->type() == HoldingSpaceItem::Type::kCameraAppScanPdf ||
+                 item->type() == HoldingSpaceItem::Type::kCameraAppVideoGif ||
+                 item->type() == HoldingSpaceItem::Type::kCameraAppVideoMp4);
+          return false;
+        }
         const auto item_count = ++item_counts_per_section_id[section->id];
         return section->max_item_count && item_count > *section->max_item_count;
       },
diff --git a/ash/public/cpp/holding_space/holding_space_model_unittest.cc b/ash/public/cpp/holding_space/holding_space_model_unittest.cc
index 10a4db1..947d6d74 100644
--- a/ash/public/cpp/holding_space/holding_space_model_unittest.cc
+++ b/ash/public/cpp/holding_space/holding_space_model_unittest.cc
@@ -572,9 +572,18 @@
   EXPECT_EQ(model().items().size(), 0u);
 
   // Cache the section to which the parameterized type belongs.
-  const HoldingSpaceSection* section =
-      GetHoldingSpaceSection(GetHoldingSpaceItemType());
-  ASSERT_TRUE(section);
+  const HoldingSpaceItem::Type type = GetHoldingSpaceItemType();
+  const HoldingSpaceSection* section = GetHoldingSpaceSection(type);
+
+  if (!section) {
+    // TODO(http://b/274484210): Update views for camera app types.
+    EXPECT_TRUE(type == HoldingSpaceItem::Type::kCameraAppPhoto ||
+                type == HoldingSpaceItem::Type::kCameraAppScanJpg ||
+                type == HoldingSpaceItem::Type::kCameraAppScanPdf ||
+                type == HoldingSpaceItem::Type::kCameraAppVideoGif ||
+                type == HoldingSpaceItem::Type::kCameraAppVideoMp4);
+    return;
+  }
 
   // Add the maximum count of items allowed for the section or some high number
   // if the section does not specify a maximum item count restriction.
diff --git a/ash/public/cpp/holding_space/holding_space_section_unittest.cc b/ash/public/cpp/holding_space/holding_space_section_unittest.cc
index 02139085..48ac1f1 100644
--- a/ash/public/cpp/holding_space/holding_space_section_unittest.cc
+++ b/ash/public/cpp/holding_space/holding_space_section_unittest.cc
@@ -34,9 +34,14 @@
 }
 
 void ExpectSection(const HoldingSpaceSection* section,
-                   HoldingSpaceSectionId expected_id) {
-  ASSERT_TRUE(section);
-  switch (expected_id) {
+                   const absl::optional<HoldingSpaceSectionId>& expected_id) {
+  if (!section) {
+    // TODO(http://b/274484210): Update views for camera app types.
+    EXPECT_FALSE(expected_id.has_value());
+    return;
+  }
+  ASSERT_TRUE(expected_id.has_value());
+  switch (expected_id.value()) {
     case HoldingSpaceSectionId::kDownloads:
       EXPECT_EQ(section->id, HoldingSpaceSectionId::kDownloads);
       EXPECT_THAT(section->supported_types,
@@ -124,8 +129,16 @@
       case HoldingSpaceItem::Type::kScreenshot:
         id = HoldingSpaceSectionId::kScreenCaptures;
         break;
+      case HoldingSpaceItem::Type::kCameraAppPhoto:
+      case HoldingSpaceItem::Type::kCameraAppScanJpg:
+      case HoldingSpaceItem::Type::kCameraAppScanPdf:
+      case HoldingSpaceItem::Type::kCameraAppVideoGif:
+      case HoldingSpaceItem::Type::kCameraAppVideoMp4:
+        // TODO(http://b/274484210): Update views for camera app types.
+        id = absl::nullopt;
+        break;
     }
-    ExpectSection(GetHoldingSpaceSection(type), id.value());
+    ExpectSection(GetHoldingSpaceSection(type), id);
   }
 }
 
diff --git a/ash/public/cpp/holding_space/holding_space_util.cc b/ash/public/cpp/holding_space/holding_space_util.cc
index d890e51..f0cdcea 100644
--- a/ash/public/cpp/holding_space/holding_space_util.cc
+++ b/ash/public/cpp/holding_space/holding_space_util.cc
@@ -13,6 +13,11 @@
   gfx::Size max_size;
   switch (type) {
     case HoldingSpaceItem::Type::kArcDownload:
+    case HoldingSpaceItem::Type::kCameraAppPhoto:
+    case HoldingSpaceItem::Type::kCameraAppScanJpg:
+    case HoldingSpaceItem::Type::kCameraAppScanPdf:
+    case HoldingSpaceItem::Type::kCameraAppVideoGif:
+    case HoldingSpaceItem::Type::kCameraAppVideoMp4:
     case HoldingSpaceItem::Type::kDiagnosticsLog:
     case HoldingSpaceItem::Type::kDownload:
     case HoldingSpaceItem::Type::kDriveSuggestion:
@@ -75,6 +80,16 @@
   switch (type) {
     case HoldingSpaceItem::Type::kArcDownload:
       return "ArcDownload";
+    case HoldingSpaceItem::Type::kCameraAppPhoto:
+      return "CameraAppPhoto";
+    case HoldingSpaceItem::Type::kCameraAppScanJpg:
+      return "CameraAppScanJpg";
+    case HoldingSpaceItem::Type::kCameraAppScanPdf:
+      return "CameraAppScanPdf";
+    case HoldingSpaceItem::Type::kCameraAppVideoGif:
+      return "CameraAppVideoGif";
+    case HoldingSpaceItem::Type::kCameraAppVideoMp4:
+      return "CameraAppVideoMp4";
     case HoldingSpaceItem::Type::kDiagnosticsLog:
       return "DiagnosticsLog";
     case HoldingSpaceItem::Type::kDownload:
diff --git a/ash/public/cpp/holding_space/holding_space_util_unittest.cc b/ash/public/cpp/holding_space/holding_space_util_unittest.cc
index 76e260f..c667567 100644
--- a/ash/public/cpp/holding_space/holding_space_util_unittest.cc
+++ b/ash/public/cpp/holding_space/holding_space_util_unittest.cc
@@ -25,6 +25,21 @@
       case HoldingSpaceItem::Type::kArcDownload:
         expected_string = "ArcDownload";
         break;
+      case HoldingSpaceItem::Type::kCameraAppPhoto:
+        expected_string = "CameraAppPhoto";
+        break;
+      case HoldingSpaceItem::Type::kCameraAppScanJpg:
+        expected_string = "CameraAppScanJpg";
+        break;
+      case HoldingSpaceItem::Type::kCameraAppScanPdf:
+        expected_string = "CameraAppScanPdf";
+        break;
+      case HoldingSpaceItem::Type::kCameraAppVideoGif:
+        expected_string = "CameraAppVideoGif";
+        break;
+      case HoldingSpaceItem::Type::kCameraAppVideoMp4:
+        expected_string = "CameraAppVideoMp4";
+        break;
       case HoldingSpaceItem::Type::kDiagnosticsLog:
         expected_string = "DiagnosticsLog";
         break;
diff --git a/ash/public/cpp/holding_space/mock_holding_space_client.h b/ash/public/cpp/holding_space/mock_holding_space_client.h
index f0afc71..09f1deb 100644
--- a/ash/public/cpp/holding_space/mock_holding_space_client.h
+++ b/ash/public/cpp/holding_space/mock_holding_space_client.h
@@ -27,6 +27,10 @@
               AddDiagnosticsLog,
               (const base::FilePath& file_path),
               (override));
+  MOCK_METHOD(const std::string&,
+              AddItemOfType,
+              (HoldingSpaceItem::Type type, const base::FilePath& file_path),
+              (override));
   MOCK_METHOD(void,
               AddScreenCapture,
               (HoldingSpaceItem::Type, const base::FilePath& file_path),
diff --git a/ash/public/cpp/system_tray_client.h b/ash/public/cpp/system_tray_client.h
index f86a451d..459f44e 100644
--- a/ash/public/cpp/system_tray_client.h
+++ b/ash/public/cpp/system_tray_client.h
@@ -182,6 +182,9 @@
   // reached end of life.
   virtual void ShowEolInfoPage() = 0;
 
+  // Records a UMA metric that the end of life notice was shown.
+  virtual void RecordEolNoticeShown() = 0;
+
   // Returns 'true' if the user preference is set to allow users to submit
   // feedback, 'false' otherwise.
   virtual bool IsUserFeedbackEnabled() = 0;
diff --git a/ash/public/cpp/test/test_system_tray_client.cc b/ash/public/cpp/test/test_system_tray_client.cc
index 5a56a41..188953a 100644
--- a/ash/public/cpp/test/test_system_tray_client.cc
+++ b/ash/public/cpp/test/test_system_tray_client.cc
@@ -159,4 +159,6 @@
   ++show_eol_info_count_;
 }
 
+void TestSystemTrayClient::RecordEolNoticeShown() {}
+
 }  // namespace ash
diff --git a/ash/public/cpp/test/test_system_tray_client.h b/ash/public/cpp/test/test_system_tray_client.h
index 989b3e80..d77d90f 100644
--- a/ash/public/cpp/test/test_system_tray_client.h
+++ b/ash/public/cpp/test/test_system_tray_client.h
@@ -73,6 +73,7 @@
   void ShowAudioSettings() override;
   bool IsUserFeedbackEnabled() override;
   void ShowEolInfoPage() override;
+  void RecordEolNoticeShown() override;
 
   int show_bluetooth_settings_count() const {
     return show_bluetooth_settings_count_;
diff --git a/ash/public/mojom/input_device_settings.mojom b/ash/public/mojom/input_device_settings.mojom
index c6127216..038952e 100644
--- a/ash/public/mojom/input_device_settings.mojom
+++ b/ash/public/mojom/input_device_settings.mojom
@@ -44,9 +44,6 @@
   map<ui.mojom.ModifierKey, ui.mojom.ModifierKey> modifier_remappings;
   bool top_row_are_fkeys;
   bool suppress_meta_fkey_rewrites;
-  bool auto_repeat_enabled;
-  mojo_base.mojom.TimeDelta auto_repeat_delay;
-  mojo_base.mojom.TimeDelta auto_repeat_interval;
 };
 
 // Contains all information needed to display, apply, and update mouse
diff --git a/ash/system/holding_space/holding_space_item_screen_capture_view.cc b/ash/system/holding_space/holding_space_item_screen_capture_view.cc
index 553def9a0..1e524811 100644
--- a/ash/system/holding_space/holding_space_item_screen_capture_view.cc
+++ b/ash/system/holding_space/holding_space_item_screen_capture_view.cc
@@ -46,6 +46,11 @@
     case HoldingSpaceItem::Type::kScreenRecordingGif:
       return &kGifIcon;
     case HoldingSpaceItem::Type::kArcDownload:
+    case HoldingSpaceItem::Type::kCameraAppPhoto:
+    case HoldingSpaceItem::Type::kCameraAppScanJpg:
+    case HoldingSpaceItem::Type::kCameraAppScanPdf:
+    case HoldingSpaceItem::Type::kCameraAppVideoGif:
+    case HoldingSpaceItem::Type::kCameraAppVideoMp4:
     case HoldingSpaceItem::Type::kDiagnosticsLog:
     case HoldingSpaceItem::Type::kDriveSuggestion:
     case HoldingSpaceItem::Type::kDownload:
diff --git a/ash/system/holding_space/holding_space_tray_unittest.cc b/ash/system/holding_space/holding_space_tray_unittest.cc
index 4b41e98..4d76e5c 100644
--- a/ash/system/holding_space/holding_space_tray_unittest.cc
+++ b/ash/system/holding_space/holding_space_tray_unittest.cc
@@ -3940,7 +3940,16 @@
 
   // Expect and cache a single holding space item view.
   std::vector<views::View*> item_views = test_api()->GetHoldingSpaceItemViews();
-  ASSERT_EQ(item_views.size(), 1u);
+  if (item_views.size() != 1u) {
+    // TODO(http://b/274484210): Update views for camera app types.
+    EXPECT_EQ(item_views.size(), 0u);
+    EXPECT_TRUE(item->type() == HoldingSpaceItem::Type::kCameraAppPhoto ||
+                item->type() == HoldingSpaceItem::Type::kCameraAppScanJpg ||
+                item->type() == HoldingSpaceItem::Type::kCameraAppScanPdf ||
+                item->type() == HoldingSpaceItem::Type::kCameraAppVideoGif ||
+                item->type() == HoldingSpaceItem::Type::kCameraAppVideoMp4);
+    return;
+  }
 
   // Initially a primary and secondary action should not be shown as the holding
   // space item is not being hovered over.
diff --git a/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_impl.cc b/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_impl.cc
index fd806c0..c9107f0 100644
--- a/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_impl.cc
+++ b/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_impl.cc
@@ -58,15 +58,20 @@
          {mojom::MetaKey::kCommand,
           ::prefs::kLanguageRemapExternalCommandKeyTo}});
 
-mojom::KeyboardSettingsPtr GetDefaultKeyboardSettings(bool is_external) {
+mojom::KeyboardSettingsPtr GetDefaultKeyboardSettings(bool is_external,
+                                                      mojom::MetaKey meta_key) {
   mojom::KeyboardSettingsPtr settings = mojom::KeyboardSettings::New();
-  settings->auto_repeat_delay = kDefaultAutoRepeatDelay;
-  settings->auto_repeat_interval = kDefaultAutoRepeatInterval;
-  settings->auto_repeat_enabled = kDefaultAutoRepeatEnabled;
   settings->suppress_meta_fkey_rewrites = kDefaultSuppressMetaFKeyRewrites;
   // This setting should be enabled by default for external keyboards.
   settings->top_row_are_fkeys =
       is_external ? kDefaultTopRowAreFKeysExternal : kDefaultTopRowAreFKeys;
+  // Switch control and command for Apple keyboards.
+  if (meta_key == mojom::MetaKey::kCommand) {
+    settings->modifier_remappings[ui::mojom::ModifierKey::kControl] =
+        ui::mojom::ModifierKey::kMeta;
+    settings->modifier_remappings[ui::mojom::ModifierKey::kMeta] =
+        ui::mojom::ModifierKey::kControl;
+  }
   return settings;
 }
 
@@ -101,12 +106,6 @@
     const mojom::Keyboard& keyboard,
     ForceKeyboardSettingPersistence& force_persistence) {
   mojom::KeyboardSettingsPtr settings = mojom::KeyboardSettings::New();
-  settings->auto_repeat_delay =
-      base::Milliseconds(prefs->GetInteger(prefs::kXkbAutoRepeatDelay));
-  settings->auto_repeat_interval =
-      base::Milliseconds(prefs->GetInteger(prefs::kXkbAutoRepeatInterval));
-  settings->auto_repeat_enabled =
-      prefs->GetBoolean(prefs::kXkbAutoRepeatEnabled);
 
   const auto* top_row_are_fkeys_preference =
       prefs->GetUserPrefValue(prefs::kSendFunctionKeys);
@@ -137,15 +136,6 @@
     const mojom::Keyboard& keyboard,
     const base::Value::Dict& settings_dict) {
   mojom::KeyboardSettingsPtr settings = mojom::KeyboardSettings::New();
-  settings->auto_repeat_enabled =
-      settings_dict.FindBool(prefs::kKeyboardSettingAutoRepeatEnabled)
-          .value_or(kDefaultAutoRepeatEnabled);
-  settings->auto_repeat_delay = base::Milliseconds(
-      settings_dict.FindInt(prefs::kKeyboardSettingAutoRepeatDelay)
-          .value_or(kDefaultAutoRepeatDelay.InMilliseconds()));
-  settings->auto_repeat_interval = base::Milliseconds(
-      settings_dict.FindInt(prefs::kKeyboardSettingAutoRepeatInterval)
-          .value_or(kDefaultAutoRepeatInterval.InMilliseconds()));
   settings->suppress_meta_fkey_rewrites =
       settings_dict.FindBool(prefs::kKeyboardSettingSuppressMetaFKeyRewrites)
           .value_or(kDefaultSuppressMetaFKeyRewrites);
@@ -202,14 +192,6 @@
 
   // Populate `settings_dict` with all settings in `settings`.
   base::Value::Dict settings_dict;
-  settings_dict.Set(
-      prefs::kKeyboardSettingAutoRepeatDelay,
-      static_cast<int>(settings.auto_repeat_delay.InMilliseconds()));
-  settings_dict.Set(
-      prefs::kKeyboardSettingAutoRepeatInterval,
-      static_cast<int>(settings.auto_repeat_interval.InMilliseconds()));
-  settings_dict.Set(prefs::kKeyboardSettingAutoRepeatEnabled,
-                    settings.auto_repeat_enabled);
 
   // Settings should only be persisted if one or more of the following is true:
   // - Setting was previously persisted to storage
@@ -271,7 +253,8 @@
     PrefService* pref_service,
     mojom::Keyboard* keyboard) {
   if (!pref_service) {
-    keyboard->settings = GetDefaultKeyboardSettings(keyboard->is_external);
+    keyboard->settings =
+        GetDefaultKeyboardSettings(keyboard->is_external, keyboard->meta_key);
     return;
   }
 
@@ -289,7 +272,8 @@
     keyboard->settings = GetKeyboardSettingsFromGlobalPrefs(
         pref_service, *keyboard, force_persistence);
   } else {
-    keyboard->settings = GetDefaultKeyboardSettings(keyboard->is_external);
+    keyboard->settings =
+        GetDefaultKeyboardSettings(keyboard->is_external, keyboard->meta_key);
   }
   DCHECK(keyboard->settings);
 
diff --git a/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_unittest.cc b/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_unittest.cc
index d5a608d..42d4f2f 100644
--- a/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_unittest.cc
+++ b/ash/system/input_device_settings/pref_handlers/keyboard_pref_handler_unittest.cc
@@ -31,34 +31,22 @@
 const std::string kKeyboardKey2 = "device_key2";
 const std::string kKeyboardKey3 = "device_key3";
 
-const int kGlobalAutoRepeatDelay = 1000;
-const int kGlobalAutoRepeatInterval = 1000;
-const bool kGlobalAutoRepeatEnabled = false;
 const bool kGlobalSendFunctionKeys = false;
 
 const mojom::KeyboardSettings kKeyboardSettingsDefault(
     /*modifier_remappings=*/{},
     /*top_row_are_fkeys=*/kDefaultTopRowAreFKeys,
-    /*suppress_meta_fkey_rewrites=*/kDefaultSuppressMetaFKeyRewrites,
-    /*auto_repeat_enabled=*/kDefaultAutoRepeatEnabled,
-    /*auto_repeat_delay=*/kDefaultAutoRepeatDelay,
-    /*auto_repeat_interval=*/kDefaultAutoRepeatInterval);
+    /*suppress_meta_fkey_rewrites=*/kDefaultSuppressMetaFKeyRewrites);
 
 const mojom::KeyboardSettings kKeyboardSettingsNotDefault(
     /*modifier_remappings=*/{},
     /*top_row_are_fkeys=*/!kDefaultTopRowAreFKeys,
-    /*suppress_meta_fkey_rewrites=*/!kDefaultSuppressMetaFKeyRewrites,
-    /*auto_repeat_enabled=*/kDefaultAutoRepeatEnabled,
-    /*auto_repeat_delay=*/kDefaultAutoRepeatDelay,
-    /*auto_repeat_interval=*/kDefaultAutoRepeatInterval);
+    /*suppress_meta_fkey_rewrites=*/!kDefaultSuppressMetaFKeyRewrites);
 
 const mojom::KeyboardSettings kKeyboardSettings1(
     /*modifier_remappings=*/{},
     /*top_row_are_fkeys=*/false,
-    /*suppress_meta_fkey_rewrites=*/false,
-    /*auto_repeat_enabled=*/false,
-    /*auto_repeat_delay=*/base::Milliseconds(2000),
-    /*auto_repeat_interval=*/base::Milliseconds(500));
+    /*suppress_meta_fkey_rewrites=*/false);
 
 const mojom::KeyboardSettings kKeyboardSettings2(
     /*modifier_remappings=*/{{ui::mojom::ModifierKey::kControl,
@@ -66,10 +54,7 @@
                              {ui::mojom::ModifierKey::kAssistant,
                               ui::mojom::ModifierKey::kVoid}},
     /*top_row_are_fkeys=*/true,
-    /*suppress_meta_fkey_rewrites=*/true,
-    /*auto_repeat_enabled=*/true,
-    /*auto_repeat_delay=*/base::Milliseconds(100),
-    /*auto_repeat_interval=*/base::Milliseconds(100));
+    /*suppress_meta_fkey_rewrites=*/true);
 
 const mojom::KeyboardSettings kKeyboardSettings3(
     /*modifier_remappings=*/{{ui::mojom::ModifierKey::kAlt,
@@ -81,10 +66,7 @@
                              {ui::mojom::ModifierKey::kControl,
                               ui::mojom::ModifierKey::kAssistant}},
     /*top_row_are_fkeys=*/true,
-    /*suppress_meta_fkey_rewrites=*/false,
-    /*auto_repeat_enabled=*/true,
-    /*auto_repeat_delay=*/base::Milliseconds(5000),
-    /*auto_repeat_interval=*/base::Milliseconds(3000));
+    /*suppress_meta_fkey_rewrites=*/false);
 }  // namespace
 
 class KeyboardPrefHandlerTest : public AshTestBase {
@@ -111,12 +93,6 @@
 
     pref_service_->registry()->RegisterDictionaryPref(
         prefs::kKeyboardDeviceSettingsDictPref);
-    pref_service_->registry()->RegisterIntegerPref(prefs::kXkbAutoRepeatDelay,
-                                                   kGlobalAutoRepeatDelay);
-    pref_service_->registry()->RegisterIntegerPref(
-        prefs::kXkbAutoRepeatInterval, kGlobalAutoRepeatInterval);
-    pref_service_->registry()->RegisterBooleanPref(prefs::kXkbAutoRepeatEnabled,
-                                                   kGlobalAutoRepeatEnabled);
     pref_service_->registry()->RegisterBooleanPref(prefs::kSendFunctionKeys,
                                                    kGlobalSendFunctionKeys);
 
@@ -153,23 +129,6 @@
   void CheckKeyboardSettingsAndDictAreEqual(
       const mojom::KeyboardSettings& settings,
       const base::Value::Dict& settings_dict) {
-    auto auto_repeat_delay =
-        settings_dict.FindInt(prefs::kKeyboardSettingAutoRepeatDelay);
-    ASSERT_TRUE(auto_repeat_delay.has_value());
-    EXPECT_EQ(static_cast<int>(settings.auto_repeat_delay.InMilliseconds()),
-              auto_repeat_delay);
-
-    auto auto_repeat_interval =
-        settings_dict.FindInt(prefs::kKeyboardSettingAutoRepeatInterval);
-    ASSERT_TRUE(auto_repeat_interval.has_value());
-    EXPECT_EQ(static_cast<int>(settings.auto_repeat_interval.InMilliseconds()),
-              auto_repeat_interval);
-
-    auto auto_repeat_enabled =
-        settings_dict.FindBool(prefs::kKeyboardSettingAutoRepeatEnabled);
-    ASSERT_TRUE(auto_repeat_enabled.has_value());
-    EXPECT_EQ(settings.auto_repeat_enabled, auto_repeat_enabled);
-
     auto suppress_meta_fkey_rewrites =
         settings_dict.FindBool(prefs::kKeyboardSettingSuppressMetaFKeyRewrites);
     if (suppress_meta_fkey_rewrites.has_value()) {
@@ -309,7 +268,6 @@
   mojom::KeyboardSettings updated_settings = kKeyboardSettings1;
   updated_settings.modifier_remappings = {
       {ui::mojom::ModifierKey::kAlt, ui::mojom::ModifierKey::kControl}};
-  updated_settings.auto_repeat_enabled = !updated_settings.auto_repeat_enabled;
   updated_settings.suppress_meta_fkey_rewrites =
       !updated_settings.suppress_meta_fkey_rewrites;
   updated_settings.top_row_are_fkeys = !updated_settings.top_row_are_fkeys;
@@ -335,7 +293,6 @@
 
 TEST_F(KeyboardPrefHandlerTest, NewSettingAddedRoundTrip) {
   mojom::KeyboardSettings test_settings = kKeyboardSettings1;
-  test_settings.auto_repeat_enabled = !kDefaultAutoRepeatEnabled;
   test_settings.suppress_meta_fkey_rewrites = !kDefaultSuppressMetaFKeyRewrites;
 
   CallUpdateKeyboardSettings(kKeyboardKey1, test_settings);
@@ -344,7 +301,6 @@
   auto* settings_dict = devices_dict.FindDict(kKeyboardKey1);
 
   // Remove keys from the dict to mock adding a new setting in the future.
-  settings_dict->Remove(prefs::kKeyboardSettingAutoRepeatEnabled);
   settings_dict->Remove(prefs::kKeyboardSettingSuppressMetaFKeyRewrites);
   pref_service_->SetDict(prefs::kKeyboardDeviceSettingsDictPref,
                          std::move(devices_dict));
@@ -353,13 +309,11 @@
   // "new settings" match their default values.
   mojom::KeyboardSettingsPtr settings =
       CallInitializeKeyboardSettings(kKeyboardKey1);
-  EXPECT_EQ(kDefaultAutoRepeatEnabled, settings->auto_repeat_enabled);
   EXPECT_EQ(kDefaultSuppressMetaFKeyRewrites,
             settings->suppress_meta_fkey_rewrites);
 
   // Reset "new settings" to the values that match `test_settings` and check
   // that the rest of the fields are equal.
-  settings->auto_repeat_enabled = !kDefaultAutoRepeatEnabled;
   settings->suppress_meta_fkey_rewrites = !kDefaultSuppressMetaFKeyRewrites;
   EXPECT_EQ(test_settings, *settings);
 }
@@ -485,11 +439,6 @@
   // prefs were used as defaults.
   mojom::KeyboardSettingsPtr settings =
       CallInitializeKeyboardSettings(keyboard.device_key);
-  ASSERT_EQ(settings->auto_repeat_enabled, kGlobalAutoRepeatEnabled);
-  ASSERT_EQ(settings->auto_repeat_interval,
-            base::Milliseconds(kGlobalAutoRepeatInterval));
-  ASSERT_EQ(settings->auto_repeat_delay,
-            base::Milliseconds(kGlobalAutoRepeatDelay));
   ASSERT_EQ(settings->top_row_are_fkeys, kGlobalSendFunctionKeys);
   ASSERT_EQ(settings->suppress_meta_fkey_rewrites,
             kDefaultSuppressMetaFKeyRewrites);
@@ -521,6 +470,19 @@
             ui::mojom::ModifierKey::kEscape);
 }
 
+TEST_F(KeyboardPrefHandlerTest, SwichControlAndCommandForAppleKeyboard) {
+  mojom::Keyboard keyboard;
+  keyboard.device_key = kKeyboardKey1;
+  keyboard.meta_key = mojom::MetaKey::kCommand;
+  mojom::KeyboardSettingsPtr settings =
+      CallInitializeKeyboardSettings(keyboard);
+
+  ASSERT_EQ(settings->modifier_remappings.at(ui::mojom::ModifierKey::kControl),
+            ui::mojom::ModifierKey::kMeta);
+  ASSERT_EQ(settings->modifier_remappings.at(ui::mojom::ModifierKey::kMeta),
+            ui::mojom::ModifierKey::kControl);
+}
+
 TEST_F(KeyboardPrefHandlerTest, DefaultNotPersistedUntilUpdated) {
   CallUpdateKeyboardSettings(kKeyboardKey1, kKeyboardSettingsDefault);
 
diff --git a/ash/system/message_center/message_center_utils.cc b/ash/system/message_center/message_center_utils.cc
index 66d7e00..69893aac 100644
--- a/ash/system/message_center/message_center_utils.cc
+++ b/ash/system/message_center/message_center_utils.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/message_center/message_center_utils.h"
 
+#include "ash/constants/ash_constants.h"
 #include "ash/public/cpp/metrics_util.h"
 #include "ash/public/cpp/vm_camera_mic_constants.h"
 #include "ash/root_window_controller.h"
@@ -39,9 +40,7 @@
 
 }  // namespace
 
-namespace ash {
-
-namespace message_center_utils {
+namespace ash::message_center_utils {
 
 bool CompareNotifications(message_center::Notification* n1,
                           message_center::Notification* n2) {
@@ -85,9 +84,11 @@
       [](auto* notification) {
         const std::string& notifier = notification->notifier_id().id;
 
-        // Don't count these notifications since we have `CameraMicTrayItemView`
-        // to show indicators on the systray.
-        if (notifier == kVmCameraMicNotifierId) {
+        // Don't count these notifications since we have
+        // `PrivacyIndicatorsTrayItemView` or `CameraMicTrayItemView` to show
+        // indicators on the systray.
+        if (notifier == kPrivacyIndicatorsNotifierId ||
+            notifier == kVmCameraMicNotifierId) {
           return false;
         }
 
@@ -279,6 +280,4 @@
       gfx::ToFlooredSize(resized_size));
 }
 
-}  // namespace message_center_utils
-
-}  // namespace ash
+}  // namespace ash::message_center_utils
diff --git a/ash/system/message_center/message_center_utils_unittest.cc b/ash/system/message_center/message_center_utils_unittest.cc
index 2c85d07e..c609bd4 100644
--- a/ash/system/message_center/message_center_utils_unittest.cc
+++ b/ash/system/message_center/message_center_utils_unittest.cc
@@ -4,27 +4,26 @@
 
 #include "ash/system/message_center/message_center_utils.h"
 
+#include "ash/constants/ash_constants.h"
 #include "ash/public/cpp/vm_camera_mic_constants.h"
 #include "ash/test/ash_test_base.h"
 #include "base/strings/utf_string_conversions.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/message_center/message_center.h"
 
-namespace ash {
-
-namespace message_center_utils {
+namespace ash::message_center_utils {
 
 namespace {
 
 void AddNotification(const std::string& notification_id,
-                     const std::string& app_id) {
+                     const std::string& notifier_id) {
   message_center::MessageCenter::Get()->AddNotification(
       std::make_unique<message_center::Notification>(
           message_center::NOTIFICATION_TYPE_SIMPLE, notification_id,
           u"test_title", u"test message", ui::ImageModel(),
           /*display_source=*/std::u16string(), GURL(),
           message_center::NotifierId(message_center::NotifierType::APPLICATION,
-                                     app_id),
+                                     notifier_id),
           message_center::RichNotificationData(),
           new message_center::NotificationDelegate()));
 }
@@ -45,6 +44,10 @@
   // VM camera/mic notifications are ignored by the counter.
   AddNotification("0", kVmCameraMicNotifierId);
   EXPECT_EQ(0u, GetNotificationCount());
+
+  // Privacy indicator notifications are ignored by the counter.
+  AddNotification("1", kPrivacyIndicatorsNotifierId);
+  EXPECT_EQ(0u, GetNotificationCount());
 }
 
 // Verifies that resizing the image that exceeds the binary size limit works
@@ -92,6 +95,4 @@
   EXPECT_LT(resized_image->bitmap()->computeByteSize(), kSizeLimit2);
 }
 
-}  // namespace message_center_utils
-
-}  // namespace ash
+}  // namespace ash::message_center_utils
diff --git a/ash/system/phonehub/phone_hub_ui_controller.cc b/ash/system/phonehub/phone_hub_ui_controller.cc
index bf714768..f85dfc39 100644
--- a/ash/system/phonehub/phone_hub_ui_controller.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller.cc
@@ -219,14 +219,6 @@
   if (feature_status == FeatureStatus::kEnabledButDisconnected)
     phone_hub_manager_->GetConnectionScheduler()->ScheduleConnectionNow();
 
-  if (features::IsEcheNetworkConnectionStateEnabled() &&
-      feature_status == FeatureStatus::kEnabledAndConnected) {
-    if (phone_hub_manager_->GetEcheConnectionStatusHandler()) {
-      phone_hub_manager_->GetEcheConnectionStatusHandler()
-          ->CheckConnectionStatusForUi();
-    }
-  }
-
   phone_hub_manager_->GetBrowserTabsModelProvider()->TriggerRefresh();
   RecordStatusOnBubbleOpened();
 
diff --git a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
index a02926e..b8eb5e5 100644
--- a/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
+++ b/ash/system/phonehub/phone_hub_ui_controller_unittest.cc
@@ -3,9 +3,7 @@
 // found in the LICENSE file.
 
 #include "ash/system/phonehub/phone_hub_ui_controller.h"
-#include <memory>
 
-#include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/system/eche/eche_tray.h"
 #include "ash/system/phonehub/phone_hub_view_ids.h"
@@ -47,8 +45,7 @@
 
   // AshTestBase:
   void SetUp() override {
-    feature_list_.InitWithFeatures(
-        {features::kEcheSWA, features::kEcheNetworkConnectionState}, {});
+    feature_list_.InitWithFeatures({features::kEcheSWA}, {});
 
     AshTestBase::SetUp();
 
@@ -94,11 +91,6 @@
         phone_status_model);
   }
 
-  void SetEcheConnectionStatusHandler(
-      eche_app::EcheConnectionStatusHandler* handler) {
-    phone_hub_manager_.set_eche_connection_hander(handler);
-  }
-
   std::unique_ptr<PhoneHubContentView> OpenBubbleAndCreateView() {
     controller_->HandleBubbleOpened();
     return controller_->CreateContentView(/*delegate=*/nullptr);
@@ -289,21 +281,6 @@
                                phone_hub_metrics::Screen::kPhoneConnected, 1);
 }
 
-TEST_F(PhoneHubUiControllerTest, PhoneConnected_HasConnectionHandler) {
-  base::HistogramTester histograms;
-  SetPhoneStatusModel(phonehub::CreateFakePhoneStatusModel());
-  SetEcheConnectionStatusHandler(
-      std::make_unique<eche_app::EcheConnectionStatusHandler>().get());
-  GetFeatureStatusProvider()->SetStatus(FeatureStatus::kEnabledAndConnected);
-  EXPECT_EQ(PhoneHubUiController::UiState::kPhoneConnected,
-            controller_->ui_state());
-
-  auto content_view = OpenBubbleAndCreateView();
-  EXPECT_EQ(kPhoneConnectedView, content_view->GetID());
-  histograms.ExpectBucketCount(kScreenOnOpenedMetric,
-                               phone_hub_metrics::Screen::kPhoneConnected, 1);
-}
-
 TEST_F(PhoneHubUiControllerTest, UnavailableScreenLocked) {
   base::HistogramTester histograms;
   GetFeatureStatusProvider()->SetStatus(FeatureStatus::kLockOrSuspended);
diff --git a/ash/system/privacy_hub/camera_privacy_switch_controller.cc b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
index bf1f697..497ba84 100644
--- a/ash/system/privacy_hub/camera_privacy_switch_controller.cc
+++ b/ash/system/privacy_hub/camera_privacy_switch_controller.cc
@@ -180,16 +180,8 @@
 void CameraPrivacySwitchController::OnCameraHWPrivacySwitchStateChanged(
     const std::string& device_id,
     cros::mojom::CameraPrivacySwitchState state) {
-  if (features::IsVideoConferenceEnabled()) {
-    // The `VideoConferenceTrayController` shows this info as a toast.
-    return;
-  }
-
-  if (features::IsPrivacyIndicatorsEnabled()) {
-    // Always hide if the switch was turned off.
-    if (state == cros::mojom::CameraPrivacySwitchState::OFF) {
-      turn_sw_switch_on_notification_.Hide();
-    }
+  if (features::IsVideoConferenceEnabled() ||
+      features::IsPrivacyIndicatorsEnabled()) {
     return;
   }
 
diff --git a/ash/system/unified/notification_icons_controller.cc b/ash/system/unified/notification_icons_controller.cc
index d242fc7..1b93411 100644
--- a/ash/system/unified/notification_icons_controller.cc
+++ b/ash/system/unified/notification_icons_controller.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/unified/notification_icons_controller.h"
 
+#include "ash/constants/ash_constants.h"
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/vm_camera_mic_constants.h"
 #include "ash/resources/vector_icons/vector_icons.h"
@@ -67,8 +68,10 @@
   // already indicated by another item in tray.
   if (notifier_id == kVmCameraMicNotifierId ||
       notifier_id == kBatteryNotificationNotifierId ||
-      notifier_id == kUsbNotificationNotifierId)
+      notifier_id == kUsbNotificationNotifierId ||
+      notifier_id == kPrivacyIndicatorsNotifierId) {
     return false;
+  }
 
   // We only show notification icon in the tray if it is either:
   // *   Pinned (generally used for background process such as sharing your
diff --git a/ash/system/unified/notification_icons_controller_unittest.cc b/ash/system/unified/notification_icons_controller_unittest.cc
index 59c523b1..a0fdc2ef 100644
--- a/ash/system/unified/notification_icons_controller_unittest.cc
+++ b/ash/system/unified/notification_icons_controller_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/unified/notification_icons_controller.h"
 
+#include "ash/constants/ash_constants.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/public/cpp/notification_utils.h"
@@ -50,7 +51,7 @@
 
   std::string AddNotification(bool is_pinned,
                               bool is_critical_warning,
-                              const std::string& app_id = "app") {
+                              const std::string& notifier_id = "app") {
     std::string id = base::NumberToString(notification_id_++);
 
     auto warning_level =
@@ -66,7 +67,7 @@
             u"test message", std::u16string() /*display_source */,
             GURL() /* origin_url */,
             message_center::NotifierId(
-                message_center::NotifierType::SYSTEM_COMPONENT, app_id,
+                message_center::NotifierType::SYSTEM_COMPONENT, notifier_id,
                 NotificationCatalogName::kTestCatalogName),
             rich_notification_data, nullptr /* delegate */, gfx::VectorIcon(),
             warning_level));
@@ -249,6 +250,7 @@
 
   AddNotification(true /* is_pinned */, false /* is_critical_warning */,
                   kVmCameraMicNotifierId);
+
   // VM camera/mic notification should not be shown.
   EXPECT_FALSE(
       GetNotificationIconsController()->tray_items().back()->GetVisible());
@@ -263,6 +265,24 @@
   EXPECT_EQ(2, GetNotificationIconsController()
                    ->notification_counter_view()
                    ->count_for_display_for_testing());
+
+  AddNotification(true /* is_pinned */, false /* is_critical_warning */,
+                  kPrivacyIndicatorsNotifierId);
+
+  // Privacy indicator notification should not be shown.
+  EXPECT_FALSE(
+      GetNotificationIconsController()->tray_items().back()->GetVisible());
+
+  if (!IsQsRevampEnabled()) {
+    EXPECT_FALSE(separator()->GetVisible());
+  }
+
+  // Notification count does not update for this notification (since there's
+  // another tray item for this).
+  GetNotificationIconsController()->notification_counter_view()->Update();
+  EXPECT_EQ(2, GetNotificationIconsController()
+                   ->notification_counter_view()
+                   ->count_for_display_for_testing());
 }
 
 TEST_P(NotificationIconsControllerTest, NotificationItemInQuietMode) {
diff --git a/ash/system/update/eol_notice_quick_settings_view.cc b/ash/system/update/eol_notice_quick_settings_view.cc
index 2da8940..d3782f07 100644
--- a/ash/system/update/eol_notice_quick_settings_view.cc
+++ b/ash/system/update/eol_notice_quick_settings_view.cc
@@ -88,6 +88,8 @@
 
   SetInstallFocusRingOnFocus(true);
   views::FocusRing::Get(this)->SetColorId(ui::kColorAshFocusRing);
+
+  Shell::Get()->system_tray_model()->client()->RecordEolNoticeShown();
 }
 
 EolNoticeQuickSettingsView::~EolNoticeQuickSettingsView() = default;
diff --git a/ash/webui/eche_app_ui/BUILD.gn b/ash/webui/eche_app_ui/BUILD.gn
index 787f232..3bd749e 100644
--- a/ash/webui/eche_app_ui/BUILD.gn
+++ b/ash/webui/eche_app_ui/BUILD.gn
@@ -54,6 +54,8 @@
     "apps_access_manager_impl.h",
     "apps_access_setup_operation.cc",
     "apps_access_setup_operation.h",
+    "apps_launch_info_provider.cc",
+    "apps_launch_info_provider.h",
     "eche_alert_generator.cc",
     "eche_alert_generator.h",
     "eche_app_manager.cc",
@@ -163,6 +165,7 @@
   sources = [
     "apps_access_manager_impl_unittest.cc",
     "apps_access_setup_operation_unittest.cc",
+    "apps_launch_info_provider_unittest.cc",
     "eche_alert_generator_unittest.cc",
     "eche_app_manager_unittest.cc",
     "eche_connection_scheduler_impl_unittest.cc",
diff --git a/ash/webui/eche_app_ui/apps_launch_info_provider.cc b/ash/webui/eche_app_ui/apps_launch_info_provider.cc
new file mode 100644
index 0000000..13b657f4
--- /dev/null
+++ b/ash/webui/eche_app_ui/apps_launch_info_provider.cc
@@ -0,0 +1,33 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
+
+#include "ash/webui/eche_app_ui/mojom/eche_app.mojom-shared.h"
+
+namespace ash {
+namespace eche_app {
+
+AppsLaunchInfoProvider::AppsLaunchInfoProvider(
+    EcheConnectionStatusHandler* connection_handler)
+    : eche_connection_status_handler_(connection_handler) {
+  eche_connection_status_handler_->AddObserver(this);
+}
+
+AppsLaunchInfoProvider::~AppsLaunchInfoProvider() {
+  eche_connection_status_handler_->RemoveObserver(this);
+}
+
+void AppsLaunchInfoProvider::OnConnectionStatusForUiChanged(
+    mojom::ConnectionStatus connection_status) {
+  last_connection_ = connection_status;
+}
+
+void AppsLaunchInfoProvider::SetEntryPoint(
+    mojom::AppStreamLaunchEntryPoint entry_point) {
+  entry_point_ = entry_point;
+}
+
+}  // namespace eche_app
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/webui/eche_app_ui/apps_launch_info_provider.h b/ash/webui/eche_app_ui/apps_launch_info_provider.h
new file mode 100644
index 0000000..5612cba
--- /dev/null
+++ b/ash/webui/eche_app_ui/apps_launch_info_provider.h
@@ -0,0 +1,48 @@
+// 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 ASH_WEBUI_ECHE_APP_UI_APPS_LAUNCH_INFO_PROVIDER_H_
+#define ASH_WEBUI_ECHE_APP_UI_APPS_LAUNCH_INFO_PROVIDER_H_
+
+#include <cstdint>
+#include "ash/webui/eche_app_ui/eche_connection_status_handler.h"
+#include "ash/webui/eche_app_ui/mojom/eche_app.mojom-shared.h"
+#include "ash/webui/eche_app_ui/mojom/eche_app.mojom.h"
+
+namespace ash {
+namespace eche_app {
+
+// A class to store app stream entry point and last connection status.
+class AppsLaunchInfoProvider : public EcheConnectionStatusHandler::Observer {
+ public:
+  explicit AppsLaunchInfoProvider(EcheConnectionStatusHandler*);
+  ~AppsLaunchInfoProvider() override;
+
+  AppsLaunchInfoProvider(const AppsLaunchInfoProvider&) = delete;
+  AppsLaunchInfoProvider& operator=(const AppsLaunchInfoProvider&) = delete;
+
+  // EcheConnectionStatusHandler::Observer:
+  void OnConnectionStatusForUiChanged(
+      mojom::ConnectionStatus connection_status) override;
+
+  void SetEntryPoint(mojom::AppStreamLaunchEntryPoint entry_point);
+
+  mojom::ConnectionStatus GetConnectionStatusForUi() {
+    return last_connection_;
+  }
+
+  mojom::AppStreamLaunchEntryPoint entry_point() { return entry_point_; }
+
+ private:
+  EcheConnectionStatusHandler* eche_connection_status_handler_;
+  mojom::AppStreamLaunchEntryPoint entry_point_ =
+      mojom::AppStreamLaunchEntryPoint::UNKNOWN;
+  mojom::ConnectionStatus last_connection_ =
+      mojom::ConnectionStatus::kConnectionStatusDisconnected;
+};
+
+}  // namespace eche_app
+}  // namespace ash
+
+#endif  // ASH_WEBUI_ECHE_APP_UI_APPS_LAUNCH_INFO_PROVIDER_H_
\ No newline at end of file
diff --git a/ash/webui/eche_app_ui/apps_launch_info_provider_unittest.cc b/ash/webui/eche_app_ui/apps_launch_info_provider_unittest.cc
new file mode 100644
index 0000000..7b0adf5
--- /dev/null
+++ b/ash/webui/eche_app_ui/apps_launch_info_provider_unittest.cc
@@ -0,0 +1,101 @@
+// 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 "ash/webui/eche_app_ui/apps_launch_info_provider.h"
+#include <memory>
+#include "ash/webui/eche_app_ui/eche_connection_status_handler.h"
+
+#include "ash/constants/ash_features.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash::eche_app {
+
+class AppsLaunchInfoProviderTest : public testing::Test {
+ public:
+  AppsLaunchInfoProviderTest() = default;
+  AppsLaunchInfoProviderTest(const AppsLaunchInfoProviderTest&) = delete;
+  AppsLaunchInfoProviderTest& operator=(const AppsLaunchInfoProviderTest&) =
+      delete;
+  ~AppsLaunchInfoProviderTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kEcheSWA,
+                              features::kEcheNetworkConnectionState},
+        /*disabled_features=*/{});
+
+    handler_ = std::make_unique<EcheConnectionStatusHandler>();
+    provider_ = std::make_unique<AppsLaunchInfoProvider>(handler_.get());
+  }
+
+  void TearDown() override {
+    provider_.reset();
+    handler_.reset();
+  }
+
+  void NotifyConnectionStatusForUiChanged(mojom::ConnectionStatus status) {
+    handler_->SetConnectionStatusForUi(status);
+  }
+
+  mojom::ConnectionStatus GetLastConnectionStatus() {
+    return provider_->GetConnectionStatusForUi();
+  }
+
+  void SetEntryPoint(mojom::AppStreamLaunchEntryPoint entry_point) {
+    provider_->SetEntryPoint(entry_point);
+  }
+
+  mojom::AppStreamLaunchEntryPoint GetEntryPoint() {
+    return provider_->entry_point();
+  }
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+
+ private:
+  std::unique_ptr<EcheConnectionStatusHandler> handler_;
+  std::unique_ptr<AppsLaunchInfoProvider> provider_;
+};
+
+TEST_F(AppsLaunchInfoProviderTest, OnConnectionStatusForUiChanged) {
+  EXPECT_EQ(GetLastConnectionStatus(),
+            mojom::ConnectionStatus::kConnectionStatusDisconnected);
+
+  NotifyConnectionStatusForUiChanged(
+      mojom::ConnectionStatus::kConnectionStatusConnecting);
+  EXPECT_EQ(GetLastConnectionStatus(),
+            mojom::ConnectionStatus::kConnectionStatusConnecting);
+
+  NotifyConnectionStatusForUiChanged(
+      mojom::ConnectionStatus::kConnectionStatusConnected);
+  EXPECT_EQ(GetLastConnectionStatus(),
+            mojom::ConnectionStatus::kConnectionStatusConnected);
+
+  NotifyConnectionStatusForUiChanged(
+      mojom::ConnectionStatus::kConnectionStatusFailed);
+  EXPECT_EQ(GetLastConnectionStatus(),
+            mojom::ConnectionStatus::kConnectionStatusFailed);
+
+  NotifyConnectionStatusForUiChanged(
+      mojom::ConnectionStatus::kConnectionStatusDisconnected);
+  EXPECT_EQ(GetLastConnectionStatus(),
+            mojom::ConnectionStatus::kConnectionStatusDisconnected);
+}
+
+TEST_F(AppsLaunchInfoProviderTest, SetEntryPoint) {
+  EXPECT_EQ(GetEntryPoint(), mojom::AppStreamLaunchEntryPoint::UNKNOWN);
+
+  SetEntryPoint(mojom::AppStreamLaunchEntryPoint::NOTIFICATION);
+  EXPECT_EQ(GetEntryPoint(), mojom::AppStreamLaunchEntryPoint::NOTIFICATION);
+
+  SetEntryPoint(mojom::AppStreamLaunchEntryPoint::APPS_LIST);
+  EXPECT_EQ(GetEntryPoint(), mojom::AppStreamLaunchEntryPoint::APPS_LIST);
+
+  SetEntryPoint(mojom::AppStreamLaunchEntryPoint::RECENT_APPS);
+  EXPECT_EQ(GetEntryPoint(), mojom::AppStreamLaunchEntryPoint::RECENT_APPS);
+}
+
+}  // namespace ash::eche_app
\ No newline at end of file
diff --git a/ash/webui/eche_app_ui/eche_app_manager.cc b/ash/webui/eche_app_ui/eche_app_manager.cc
index 0dab695..11d3f4c2 100644
--- a/ash/webui/eche_app_ui/eche_app_manager.cc
+++ b/ash/webui/eche_app_ui/eche_app_manager.cc
@@ -9,6 +9,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/network_config_service.h"
 #include "ash/webui/eche_app_ui/apps_access_manager_impl.h"
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
 #include "ash/webui/eche_app_ui/eche_alert_generator.h"
 #include "ash/webui/eche_app_ui/eche_connection_metrics_recorder.h"
 #include "ash/webui/eche_app_ui/eche_connection_scheduler_impl.h"
@@ -59,18 +60,23 @@
           device_sync_client,
           multidevice_setup_client,
           connection_manager_.get())),
+      eche_connection_status_handler_(
+          std::make_unique<EcheConnectionStatusHandler>()),
       launch_app_helper_(
           std::make_unique<LaunchAppHelper>(phone_hub_manager,
                                             launch_eche_app_function,
                                             launch_notification_function,
                                             close_notification_function)),
+      apps_launch_info_provider_(std::make_unique<AppsLaunchInfoProvider>(
+          eche_connection_status_handler_.get())),
       stream_status_change_handler_(
           std::make_unique<EcheStreamStatusChangeHandler>()),
       eche_notification_click_handler_(
           std::make_unique<EcheNotificationClickHandler>(
               phone_hub_manager,
               feature_status_provider_.get(),
-              launch_app_helper_.get())),
+              launch_app_helper_.get(),
+              apps_launch_info_provider_.get())),
       connection_scheduler_(std::make_unique<EcheConnectionSchedulerImpl>(
           connection_manager_.get(),
           feature_status_provider_.get())),
@@ -95,7 +101,8 @@
               phone_hub_manager,
               feature_status_provider_.get(),
               launch_app_helper_.get(),
-              stream_status_change_handler_.get())),
+              stream_status_change_handler_.get(),
+              apps_launch_info_provider_.get())),
       alert_generator_(
           std::make_unique<EcheAlertGenerator>(launch_app_helper_.get(),
                                                pref_service)),
@@ -111,9 +118,7 @@
               stream_status_change_handler_.get(),
               feature_status_provider_.get())),
       eche_stream_orientation_observer_(
-          std::make_unique<EcheStreamOrientationObserver>()),
-      eche_connection_status_handler_(
-          std::make_unique<EcheConnectionStatusHandler>()) {
+          std::make_unique<EcheStreamOrientationObserver>()) {
   ash::GetNetworkConfigService(
       remote_cros_network_config_.BindNewPipeAndPassReceiver());
   system_info_provider_ = std::make_unique<SystemInfoProvider>(
@@ -179,7 +184,6 @@
 // NOTE: These should be destroyed in the opposite order of how these objects
 // are initialized in the constructor.
 void EcheAppManager::Shutdown() {
-  eche_connection_status_handler_.reset();
   eche_stream_orientation_observer_.reset();
   system_info_provider_.reset();
   eche_tray_stream_status_observer_.reset();
@@ -194,7 +198,9 @@
   connection_scheduler_.reset();
   eche_notification_click_handler_.reset();
   stream_status_change_handler_.reset();
+  apps_launch_info_provider_.reset();
   launch_app_helper_.reset();
+  eche_connection_status_handler_.reset();
   feature_status_provider_.reset();
   connection_manager_.reset();
 }
diff --git a/ash/webui/eche_app_ui/eche_app_manager.h b/ash/webui/eche_app_ui/eche_app_manager.h
index 2f08051..16cd4802 100644
--- a/ash/webui/eche_app_ui/eche_app_manager.h
+++ b/ash/webui/eche_app_ui/eche_app_manager.h
@@ -9,6 +9,7 @@
 
 #include <memory>
 
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
 #include "ash/webui/eche_app_ui/eche_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/eche_notification_click_handler.h"
 #include "ash/webui/eche_app_ui/eche_recent_app_click_handler.h"
@@ -42,6 +43,7 @@
 
 namespace eche_app {
 
+class AppsLaunchInfoProvider;
 class EcheConnector;
 class EcheMessageReceiver;
 class EcheAlertGenerator;
@@ -113,7 +115,9 @@
  private:
   std::unique_ptr<secure_channel::ConnectionManager> connection_manager_;
   std::unique_ptr<EcheFeatureStatusProvider> feature_status_provider_;
+  std::unique_ptr<EcheConnectionStatusHandler> eche_connection_status_handler_;
   std::unique_ptr<LaunchAppHelper> launch_app_helper_;
+  std::unique_ptr<AppsLaunchInfoProvider> apps_launch_info_provider_;
   std::unique_ptr<EcheStreamStatusChangeHandler> stream_status_change_handler_;
   std::unique_ptr<EcheNotificationClickHandler>
       eche_notification_click_handler_;
@@ -133,7 +137,6 @@
       eche_tray_stream_status_observer_;
   std::unique_ptr<EcheStreamOrientationObserver>
       eche_stream_orientation_observer_;
-  std::unique_ptr<EcheConnectionStatusHandler> eche_connection_status_handler_;
 };
 
 }  // namespace eche_app
diff --git a/ash/webui/eche_app_ui/eche_connection_status_handler.cc b/ash/webui/eche_app_ui/eche_connection_status_handler.cc
index 83f933cc..c266690 100644
--- a/ash/webui/eche_app_ui/eche_connection_status_handler.cc
+++ b/ash/webui/eche_app_ui/eche_connection_status_handler.cc
@@ -102,14 +102,14 @@
     return;
   }
 
-  // TODO(b/271478560): Make the time conditionals configurable through feature
-  // flags in order to tune them in the future.
   base::TimeDelta time_since_last_check =
       base::Time::Now() - last_update_timestamp_;
-  if (time_since_last_check > base::Seconds(10)) {
+  if (time_since_last_check >
+      features::kEcheBackgroundConnectionAttemptThrottleTimeout.Get()) {
     NotifyRequestBackgroundConnectionAttempt();
   }
-  if (time_since_last_check > base::Minutes(10)) {
+  if (time_since_last_check >
+      features::kEcheConnectionStatusResetTimeout.Get()) {
     connection_status_for_ui_ =
         mojom::ConnectionStatus::kConnectionStatusConnecting;
   }
diff --git a/ash/webui/eche_app_ui/eche_connection_status_handler_unittest.cc b/ash/webui/eche_app_ui/eche_connection_status_handler_unittest.cc
index b34f936..9f2450e5 100644
--- a/ash/webui/eche_app_ui/eche_connection_status_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_connection_status_handler_unittest.cc
@@ -247,6 +247,45 @@
   EXPECT_EQ(GetNumConnectionStatusForUiChangedCalls(), 1u);
 }
 
+TEST_F(EcheConnectionStatusHandlerTest,
+       CheckConnectionStatusForUi_TimeSinceLastCheckIncreases) {
+  SetFeatureStatus(FeatureStatus::kDisconnected);
+  handler().CheckConnectionStatusForUi();
+
+  EXPECT_EQ(GetLastConnectionForUiChangedStatus(),
+            mojom::ConnectionStatus::kConnectionStatusDisconnected);
+  EXPECT_EQ(GetNumConnectionStatusForUiChangedCalls(), 0u);
+  EXPECT_EQ(GetNumRequestBackgroundConnectionAttemptCalls(), 0u);
+
+  // After more than 10 seconds pass, extra calls should happen.
+  SetFeatureStatus(FeatureStatus::kConnected);
+  task_environment_.FastForwardBy(base::Seconds(11));
+  handler().CheckConnectionStatusForUi();
+
+  EXPECT_EQ(GetLastConnectionForUiChangedStatus(),
+            mojom::ConnectionStatus::kConnectionStatusConnecting);
+  EXPECT_EQ(GetNumConnectionStatusForUiChangedCalls(), 1u);
+  EXPECT_EQ(GetNumRequestBackgroundConnectionAttemptCalls(), 1u);
+
+  // Reset to Disconnected
+  handler().SetConnectionStatusForUi(
+      mojom::ConnectionStatus::kConnectionStatusDisconnected);
+  EXPECT_EQ(GetNumConnectionStatusForUiChangedCalls(), 2u);
+  EXPECT_EQ(GetNumRequestBackgroundConnectionAttemptCalls(), 1u);
+
+  EXPECT_EQ(GetLastConnectionForUiChangedStatus(),
+            mojom::ConnectionStatus::kConnectionStatusDisconnected);
+
+  // After more than 10 minutes pass, state should go back to Connecting.
+  task_environment_.FastForwardBy(base::Minutes(11));
+  handler().CheckConnectionStatusForUi();
+
+  EXPECT_EQ(GetLastConnectionForUiChangedStatus(),
+            mojom::ConnectionStatus::kConnectionStatusConnecting);
+  EXPECT_EQ(GetNumConnectionStatusForUiChangedCalls(), 3u);
+  EXPECT_EQ(GetNumRequestBackgroundConnectionAttemptCalls(), 2u);
+}
+
 TEST_F(EcheConnectionStatusHandlerTest, SetConnectionStatusForUi) {
   handler().SetConnectionStatusForUi(
       mojom::ConnectionStatus::kConnectionStatusDisconnected);
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.cc b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
index 6987a623..a95bbbc 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.cc
@@ -6,6 +6,7 @@
 
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
 #include "base/metrics/histogram_functions.h"
 #include "chromeos/ash/components/multidevice/logging/logging.h"
@@ -17,9 +18,11 @@
 EcheNotificationClickHandler::EcheNotificationClickHandler(
     phonehub::PhoneHubManager* phone_hub_manager,
     FeatureStatusProvider* feature_status_provider,
-    LaunchAppHelper* launch_app_helper)
+    LaunchAppHelper* launch_app_helper,
+    AppsLaunchInfoProvider* apps_launch_info_provider)
     : feature_status_provider_(feature_status_provider),
-      launch_app_helper_(launch_app_helper) {
+      launch_app_helper_(launch_app_helper),
+      apps_launch_info_provider_(apps_launch_info_provider) {
   handler_ = phone_hub_manager->GetNotificationInteractionHandler();
   phone_model_ = phone_hub_manager->GetPhoneModel();
   feature_status_provider_->AddObserver(this);
@@ -49,6 +52,8 @@
       base::UmaHistogramEnumeration(
           "Eche.AppStream.LaunchAttempt",
           mojom::AppStreamLaunchEntryPoint::NOTIFICATION);
+      apps_launch_info_provider_->SetEntryPoint(
+          mojom::AppStreamLaunchEntryPoint::NOTIFICATION);
       launch_app_helper_->LaunchEcheApp(
           notification_id, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler.h b/ash/webui/eche_app_ui/eche_notification_click_handler.h
index f0b4fba..a9c450b 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler.h
@@ -22,14 +22,17 @@
 namespace eche_app {
 
 class LaunchAppHelper;
+class AppsLaunchInfoProvider;
 
 // Handles notification clicks originating from Phone Hub notifications.
 class EcheNotificationClickHandler : public phonehub::NotificationClickHandler,
                                      public FeatureStatusProvider::Observer {
  public:
-  EcheNotificationClickHandler(phonehub::PhoneHubManager* phone_hub_manager,
-                               FeatureStatusProvider* feature_status_provider,
-                               LaunchAppHelper* launch_app_helper);
+  EcheNotificationClickHandler(
+      phonehub::PhoneHubManager* phone_hub_manager,
+      FeatureStatusProvider* feature_status_provider,
+      LaunchAppHelper* launch_app_helper,
+      AppsLaunchInfoProvider* apps_launch_info_provider);
   ~EcheNotificationClickHandler() override;
 
   EcheNotificationClickHandler(const EcheNotificationClickHandler&) = delete;
@@ -51,6 +54,7 @@
   phonehub::PhoneModel* phone_model_;
   FeatureStatusProvider* feature_status_provider_;
   LaunchAppHelper* launch_app_helper_;
+  AppsLaunchInfoProvider* apps_launch_info_provider_;
   bool is_click_handler_set_ = false;
 };
 
diff --git a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
index 05ef6ea..8203f4c 100644
--- a/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_notification_click_handler_unittest.cc
@@ -7,6 +7,8 @@
 #include <string>
 
 #include "ash/constants/ash_features.h"
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
+#include "ash/webui/eche_app_ui/eche_connection_status_handler.h"
 #include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
@@ -50,12 +52,18 @@
         base::BindRepeating(
             &EcheNotificationClickHandlerTest::FakeCloseNotificationFunction,
             base::Unretained(this)));
+    connection_status_handler_ =
+        std::make_unique<eche_app::EcheConnectionStatusHandler>();
+    apps_launch_info_provider_ = std::make_unique<AppsLaunchInfoProvider>(
+        connection_status_handler_.get());
     handler_ = std::make_unique<EcheNotificationClickHandler>(
         &fake_phone_hub_manager_, &fake_feature_status_provider_,
-        launch_app_helper_.get());
+        launch_app_helper_.get(), apps_launch_info_provider_.get());
   }
 
   void TearDown() override {
+    connection_status_handler_.reset();
+    apps_launch_info_provider_.reset();
     launch_app_helper_.reset();
     handler_.reset();
   }
@@ -116,6 +124,9 @@
   base::test::ScopedFeatureList scoped_feature_list_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
   std::unique_ptr<FakeLaunchAppHelper> launch_app_helper_;
+  std::unique_ptr<eche_app::EcheConnectionStatusHandler>
+      connection_status_handler_;
+  std::unique_ptr<AppsLaunchInfoProvider> apps_launch_info_provider_;
   size_t num_notifications_shown_ = 0;
   size_t num_app_launch_ = 0;
 };
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
index 11bcd4a2..6ee72051 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.cc
@@ -8,6 +8,7 @@
 #include "ash/shell.h"
 #include "ash/system/eche/eche_tray.h"
 #include "ash/system/phonehub/phone_hub_ui_controller.h"
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
 #include "ash/webui/eche_app_ui/launch_app_helper.h"
 #include "base/metrics/histogram_functions.h"
 #include "chromeos/ash/components/phonehub/phone_hub_manager.h"
@@ -19,11 +20,13 @@
     phonehub::PhoneHubManager* phone_hub_manager,
     FeatureStatusProvider* feature_status_provider,
     LaunchAppHelper* launch_app_helper,
-    EcheStreamStatusChangeHandler* stream_status_change_handler)
+    EcheStreamStatusChangeHandler* stream_status_change_handler,
+    AppsLaunchInfoProvider* apps_launch_info_provider)
     : phone_hub_manager_(phone_hub_manager),
       feature_status_provider_(feature_status_provider),
       launch_app_helper_(launch_app_helper),
-      stream_status_change_handler_(stream_status_change_handler) {
+      stream_status_change_handler_(stream_status_change_handler),
+      apps_launch_info_provider_(apps_launch_info_provider) {
   notification_handler_ =
       phone_hub_manager->GetNotificationInteractionHandler();
   recent_apps_handler_ = phone_hub_manager->GetRecentAppsInteractionHandler();
@@ -84,6 +87,7 @@
       base::UmaHistogramEnumeration("Eche.AppStream.LaunchAttempt", entrypoint);
 
       to_stream_apps_.emplace_back(app_metadata);
+      apps_launch_info_provider_->SetEntryPoint(entrypoint);
       launch_app_helper_->LaunchEcheApp(
           /*notification_id=*/absl::nullopt, app_metadata.package_name,
           app_metadata.visible_app_name, app_metadata.user_id,
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
index 2116e77..bea6521 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler.h
@@ -24,6 +24,7 @@
 namespace eche_app {
 
 class LaunchAppHelper;
+class AppsLaunchInfoProvider;
 
 // Handles recent app clicks originating from Phone Hub recent apps.
 class EcheRecentAppClickHandler
@@ -36,7 +37,8 @@
       phonehub::PhoneHubManager* phone_hub_manager,
       FeatureStatusProvider* feature_status_provider,
       LaunchAppHelper* launch_app_helper,
-      EcheStreamStatusChangeHandler* stream_status_change_handler);
+      EcheStreamStatusChangeHandler* stream_status_change_handler,
+      AppsLaunchInfoProvider* apps_launch_info_provider);
   ~EcheRecentAppClickHandler() override;
 
   EcheRecentAppClickHandler(const EcheRecentAppClickHandler&) = delete;
@@ -70,6 +72,7 @@
   LaunchAppHelper* launch_app_helper_;
   EcheStreamStatusChangeHandler* stream_status_change_handler_;
   std::vector<phonehub::Notification::AppMetadata> to_stream_apps_;
+  AppsLaunchInfoProvider* apps_launch_info_provider_;
   bool is_click_handler_set_ = false;
   bool is_stream_started_ = false;
 };
diff --git a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
index 106d806..3407f3f 100644
--- a/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
+++ b/ash/webui/eche_app_ui/eche_recent_app_click_handler_unittest.cc
@@ -4,9 +4,12 @@
 
 #include "ash/webui/eche_app_ui/eche_recent_app_click_handler.h"
 
+#include <memory>
 #include <string>
 
 #include "ash/constants/ash_features.h"
+#include "ash/webui/eche_app_ui/apps_launch_info_provider.h"
+#include "ash/webui/eche_app_ui/eche_connection_status_handler.h"
 #include "ash/webui/eche_app_ui/eche_stream_status_change_handler.h"
 #include "ash/webui/eche_app_ui/fake_feature_status_provider.h"
 #include "ash/webui/eche_app_ui/fake_launch_app_helper.h"
@@ -50,14 +53,21 @@
         base::BindRepeating(
             &EcheRecentAppClickHandlerTest::FakeCloseNotificationFunction,
             base::Unretained(this)));
+    connection_status_handler_ =
+        std::make_unique<eche_app::EcheConnectionStatusHandler>();
+    apps_launch_info_provider_ = std::make_unique<AppsLaunchInfoProvider>(
+        connection_status_handler_.get());
     stream_status_change_handler_ =
         std::make_unique<EcheStreamStatusChangeHandler>();
     handler_ = std::make_unique<EcheRecentAppClickHandler>(
         &fake_phone_hub_manager_, &fake_feature_status_provider_,
-        launch_app_helper_.get(), stream_status_change_handler_.get());
+        launch_app_helper_.get(), stream_status_change_handler_.get(),
+        apps_launch_info_provider_.get());
   }
 
   void TearDown() override {
+    apps_launch_info_provider_.reset();
+    connection_status_handler_.reset();
     launch_app_helper_.reset();
     handler_.reset();
     stream_status_change_handler_.reset();
@@ -137,6 +147,9 @@
   std::unique_ptr<FakeLaunchAppHelper> launch_app_helper_;
   std::unique_ptr<EcheStreamStatusChangeHandler> stream_status_change_handler_;
   std::unique_ptr<EcheRecentAppClickHandler> handler_;
+  std::unique_ptr<eche_app::EcheConnectionStatusHandler>
+      connection_status_handler_;
+  std::unique_ptr<AppsLaunchInfoProvider> apps_launch_info_provider_;
   std::string package_name_;
   std::u16string visible_name_;
   int64_t user_id_;
diff --git a/ash/webui/personalization_app/resources/js/wallpaper/google_photos_albums_element.ts b/ash/webui/personalization_app/resources/js/wallpaper/google_photos_albums_element.ts
index a23acbf5..cc10bce0 100644
--- a/ash/webui/personalization_app/resources/js/wallpaper/google_photos_albums_element.ts
+++ b/ash/webui/personalization_app/resources/js/wallpaper/google_photos_albums_element.ts
@@ -301,8 +301,9 @@
   /** Returns the aria label for the specified |album|. */
   private getAlbumAriaLabel_(album: GooglePhotosAlbum|null): string|undefined {
     if (album) {
-      return album.id === PLACEHOLDER_ID ? this.i18n('ariaLabelLoading') :
-                                           album.title;
+      return album.id === PLACEHOLDER_ID ?
+          this.i18n('ariaLabelLoading') :
+          `${album.title} ${this.getSecondaryText_(album)}`;
     }
     return undefined;
   }
diff --git a/ash/webui/personalization_app/tools/gen_tsconfig.py b/ash/webui/personalization_app/tools/gen_tsconfig.py
index f3e58eb..9b9370b 100755
--- a/ash/webui/personalization_app/tools/gen_tsconfig.py
+++ b/ash/webui/personalization_app/tools/gen_tsconfig.py
@@ -73,7 +73,6 @@
     with open(out_json_path, 'r') as f:
         out_json = json.load(f)
 
-    curdir = os.path.curdir
     out_json_dir = os.path.dirname(out_json_path)
     local_json = {
         'extends': normalize_path(out_json_dir, out_json['extends']),
@@ -91,6 +90,12 @@
                 for key, value in out_json['compilerOptions']['paths'].items()
             },
         },
+        'files': [
+            # Add the .d.ts files.  Type definition only files are not
+            # picked up automatically with `rootDirs`.
+            normalize_path(out_json_dir, path)
+            for path in out_json['files'] if path.endswith('.d.ts')
+        ],
         'references': [{
             'path': normalize_path(out_json_dir, path['path'])
         } for path in out_json['references']],
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index 6b9e0f7..24d90be2 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -434,7 +434,13 @@
   base::RunLoop().RunUntilIdle();
 }
 
-TEST_F(AcceleratorConfigurationProviderTest, InitialAccelInitCalls) {
+// TODO(crbug.com/1426992): Fix flakiness and re-enable.
+#if BUILDFLAG(IS_CHROMEOS)
+#define MAYBE_InitialAccelInitCalls DISABLED_InitialAccelInitCalls
+#else
+#define MAYBE_InitialAccelInitCalls InitialAccelInitCalls
+#endif
+TEST_F(AcceleratorConfigurationProviderTest, MAYBE_InitialAccelInitCalls) {
   FakeAcceleratorsUpdatedMojoObserver mojo_observer;
   SetUpObserver(&mojo_observer);
   EXPECT_EQ(0, mojo_observer.num_times_notified());
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.cc
index b54fa49..4ab9612 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.cc
@@ -144,20 +144,6 @@
                 TextAcceleratorPart(TextAcceleratorDelimiter::kPlusSign),
                 TextAcceleratorPart(ui::KeyboardCode::VKEY_1),
                 TextAcceleratorPart(ui::KeyboardCode::VKEY_8)})},
-          {NonConfigurableActions::kBrowserFindNext,
-           NonConfigurableAcceleratorDetails(
-               IDS_AMBIENT_ACCELERATOR_FIND_NEXT,
-               {TextAcceleratorPart(ui::EF_CONTROL_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_G),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_RETURN)})},
-          {NonConfigurableActions::kBrowserFindPrevious,
-           NonConfigurableAcceleratorDetails(
-               IDS_AMBIENT_ACCELERATOR_FIND_PREVIOUS,
-               {TextAcceleratorPart(ui::EF_CONTROL_DOWN),
-                TextAcceleratorPart(ui::EF_SHIFT_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_G),
-                TextAcceleratorPart(ui::EF_SHIFT_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_RETURN)})},
           {NonConfigurableActions::kAmbientOpenLinkInTab,
            NonConfigurableAcceleratorDetails(
                IDS_AMBIENT_ACCELERATOR_OPEN_LINK_IN_TAB,
@@ -182,28 +168,10 @@
                {TextAcceleratorPart(ui::EF_ALT_DOWN),
                 TextAcceleratorPart(ui::EF_SHIFT_DOWN),
                 TextAcceleratorPart(ui::KeyboardCode::VKEY_TAB)})},
-          {NonConfigurableActions::kBrowserFocusSearch,
-           NonConfigurableAcceleratorDetails(
-               IDS_AMBIENT_ACCELERATOR_FOCUS_SEARCH,
-               {TextAcceleratorPart(ui::EF_CONTROL_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_K),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_E)})},
-          {NonConfigurableActions::kBrowserReload,
-           NonConfigurableAcceleratorDetails(
-               IDS_AMBIENT_ACCELERATOR_RELOAD,
-               {TextAcceleratorPart(ui::KeyboardCode::VKEY_BROWSER_REFRESH),
-                TextAcceleratorPart(ui::EF_CONTROL_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_R)})},
           {NonConfigurableActions::kBrowserRightClick,
            NonConfigurableAcceleratorDetails(
                IDS_AMBIENT_ACCELERATOR_RIGHT_CLICK,
                {TextAcceleratorPart(ui::EF_ALT_DOWN)})},
-          {NonConfigurableActions::kBrowserShowAppMenu,
-           NonConfigurableAcceleratorDetails(
-               IDS_AMBIENT_ACCELERATOR_SHOW_APP_MENU,
-               {TextAcceleratorPart(ui::EF_CONTROL_DOWN),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_E),
-                TextAcceleratorPart(ui::KeyboardCode::VKEY_F)})},
           {NonConfigurableActions::kAmbientLaunchNumberedApp,
            NonConfigurableAcceleratorDetails(
                IDS_AMBIENT_ACCELERATOR_LAUNCH_NUMBERED_APP,
@@ -498,6 +466,31 @@
           {NonConfigurableActions::kAmbientMoveToEndOfWord,
            NonConfigurableAcceleratorDetails(
                {ui::Accelerator(ui::VKEY_RIGHT, ui::EF_CONTROL_DOWN)})},
+          {NonConfigurableActions::kBrowserFindNext,
+           NonConfigurableAcceleratorDetails(
+               {ui::Accelerator(ui::VKEY_G, ui::EF_CONTROL_DOWN),
+                // TODO(longbowei): Confirm if we want to keep this accelerator
+                // or remove it.
+                ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)})},
+          {NonConfigurableActions::kBrowserFindPrevious,
+           NonConfigurableAcceleratorDetails(
+               {ui::Accelerator(ui::VKEY_G,
+                                ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
+                // TODO(longbowei): Confirm if we want to keep this accelerator
+                // or remove it.
+                ui::Accelerator(ui::VKEY_RETURN, ui::EF_SHIFT_DOWN)})},
+          {NonConfigurableActions::kBrowserFocusSearch,
+           NonConfigurableAcceleratorDetails(
+               {ui::Accelerator(ui::VKEY_K, ui::EF_CONTROL_DOWN),
+                ui::Accelerator(ui::VKEY_E, ui::EF_CONTROL_DOWN)})},
+          {NonConfigurableActions::kBrowserShowAppMenu,
+           NonConfigurableAcceleratorDetails(
+               {ui::Accelerator(ui::VKEY_E, ui::EF_ALT_DOWN),
+                ui::Accelerator(ui::VKEY_F, ui::EF_ALT_DOWN)})},
+          {NonConfigurableActions::kBrowserReload,
+           NonConfigurableAcceleratorDetails(
+               {ui::Accelerator(ui::VKEY_BROWSER_REFRESH, ui::EF_NONE),
+                ui::Accelerator(ui::VKEY_R, ui::EF_CONTROL_DOWN)})},
       });
   return *nonConfigurableActionsMap;
 }
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.h b/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.h
index a8c08cc0..6f8ca31 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.h
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_layout_table.h
@@ -510,7 +510,7 @@
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_APP_MENU,
      mojom::AcceleratorCategory::kBrowser,
      mojom::AcceleratorSubcategory::kGeneral,
-     /*locked=*/true, mojom::AcceleratorLayoutStyle::kText,
+     /*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
      mojom::AcceleratorSource::kAmbient},
     {NonConfigurableActions::kBrowserShowDownloads,
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_SHOW_DOWNLOADS,
@@ -540,7 +540,7 @@
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_FOCUS_SEARCH,
      mojom::AcceleratorCategory::kBrowser,
      mojom::AcceleratorSubcategory::kBrowserNavigation,
-     /*locked=*/true, mojom::AcceleratorLayoutStyle::kText,
+     /*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
      mojom::AcceleratorSource::kAmbient},
     {NonConfigurableActions::kBrowserAutoComplete,
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_AUTO_COMPLETE,
@@ -599,7 +599,7 @@
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD,
      mojom::AcceleratorCategory::kBrowser,
      mojom::AcceleratorSubcategory::kPages,
-     /*locked=*/true, mojom::AcceleratorLayoutStyle::kText,
+     /*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
      mojom::AcceleratorSource::kAmbient},
     {NonConfigurableActions::kBrowserReloadBypassingCache,
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_RELOAD_BYPASSING_CACHE,
@@ -665,13 +665,13 @@
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_NEXT,
      mojom::AcceleratorCategory::kBrowser,
      mojom::AcceleratorSubcategory::kPages,
-     /*locked=*/true, mojom::AcceleratorLayoutStyle::kText,
+     /*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
      mojom::AcceleratorSource::kAmbient},
     {NonConfigurableActions::kBrowserFindPrevious,
      IDS_BROWSER_ACCELERATOR_DESCRIPTION_FIND_PREVIOUS,
      mojom::AcceleratorCategory::kBrowser,
      mojom::AcceleratorSubcategory::kPages,
-     /*locked=*/true, mojom::AcceleratorLayoutStyle::kText,
+     /*locked=*/true, mojom::AcceleratorLayoutStyle::kDefault,
      mojom::AcceleratorSource::kAmbient},
     {NonConfigurableActions::kBrowserNewTab,
      IDS_ASH_ACCELERATOR_DESCRIPTION_NEW_TAB,
diff --git a/ash/webui/shortcut_customization_ui/resources/js/search/search_box.html b/ash/webui/shortcut_customization_ui/resources/js/search/search_box.html
index 7d84173f..e1f70c24 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/search/search_box.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/search/search_box.html
@@ -1,8 +1,88 @@
 <style>
-  /** TODO(cambickel) Replace this with the actual results implementation. */
-  #searchResultList {
-    background: white;
-    position: fixed;
+  :host {
+    --cr-toolbar-search-field-background:
+        var(--cros-toolbar-search-bg-color);
+
+    --separator-height: 8px;
+    -webkit-tap-highlight-color: transparent;
+    display: flex;
+    flex-basis: var(--cr-toolbar-field-width);
+    transition: width 150ms cubic-bezier(0.4, 0, 0.2, 1);
+    width: var(--cr-toolbar-field-width);
+  }
+
+  cr-toolbar-search-field {
+    --cr-toolbar-search-field-border-radius: 20px;
+    --cr-toolbar-search-field-paper-spinner-margin: 0 12px;
+    --cr-toolbar-search-field-input-icon-color:
+        var(--cros-icon-color-primary);
+    --cr-toolbar-search-field-input-text-color:
+        var(--cros-text-color-primary);
+    --cr-toolbar-search-field-input-caret-color: currentColor;
+    --cr-toolbar-search-field-prompt-color:
+        var(--cros-text-color-secondary);
+    --cr-toolbar-icon-button-focus-outline-color:
+        var(--cros-focus-ring-color);
+  }
+
+  :host(:focus-within) cr-toolbar-search-field {
+    --cr-toolbar-search-field-background: var(--cros-bg-color-elevation-3);
+    box-shadow: var(--cr-elevation-1);
+    min-height: var(--cr-toolbar-focused-min-height);
+  }
+
+  :host(:not(:focus-within)) cr-toolbar-search-field {
+    --cr-toolbar-search-field-cursor: pointer;
+  }
+
+  :host([has-search-query]) cr-toolbar-search-field {
+    min-height: 40px;
+  }
+
+  :host(:focus-within[should-show-dropdown]) cr-toolbar-search-field {
+    --cr-toolbar-search-field-border-radius: 20px 20px 0 0;
+    box-shadow: var(--cr-elevation-3);
+    height: 56px;
+    margin-top: var(--separator-height);
+    padding-bottom: var(--separator-height);
+  }
+
+  iron-dropdown {
+    margin-top: 72px;
+  }
+
+  iron-dropdown [slot='dropdown-content'] {
+    background-color: var(--cros-bg-color-elevation-3);
+    border-radius: 0 0 20px 20px;
+    box-shadow: var(--cr-elevation-3);
+    display: table;
+    padding-bottom: 8px;
+    width: var(--cr-toolbar-field-width);
+  }
+
+  iron-list {
+    max-height: 50vh;
+  }
+
+  #noSearchResultsContainer {
+    height: 32px;
+    line-height: 32px;
+    margin-inline-start: 24px;
+  }
+
+  /* The separator covers the top box shadow of the dropdown so that
+   * var(--cr-elevation-3) can be used instead of custom values.
+   */
+  .separator {
+    background-color: var(--cros-bg-color-elevation-3);
+    border-bottom: none;
+    border-inline-end: none;
+    border-inline-start: none;
+    border-top: 1px solid var(--cros-separator-color);
+    height: var(--separator-height);
+    margin-inline-end: 0;
+    margin-inline-start: 0;
+    margin-top: -9px;
   }
 </style>
 
@@ -11,8 +91,23 @@
         label="Search for keyboard shortcuts"
         on-keydown="onKeyDown">
 </cr-toolbar-search-field>
-<div id="searchResultList" hidden="[[!shouldShowDropdown]]">
-  <template is="dom-repeat" items="[[searchResults]]">
-    <search-result-row search-result="[[item]]"></search-result-row>
-  </template>
-</div>
+<iron-dropdown id="searchResults" opened="[[shouldShowDropdown]]"
+    allow-outside-scroll no-cancel-on-outside-click>
+  <!--  As part of iron-dropdown's behavior, the slot 'dropdown-content' is
+        hidden until iron-dropdown's opened attribute is set true, or when
+        iron-dropdown's open() is called on the JS side. -->
+  <div slot="dropdown-content">
+    <div class="separator"></div>
+    <iron-list id="searchResultList" selection-enabled
+        items="[[searchResults]]">
+      <template>
+        <search-result-row search-result="[[item]]"></search-result-row>
+      </template>
+    </iron-list>
+    <div id="noSearchResultsContainer" hidden="[[searchResultsExist]]">
+      <!-- TODO(cambickel) localize this string -->
+      No search results!
+    </div>
+  </div>
+</iron-dropdown>
+
diff --git a/ash/webui/shortcut_customization_ui/resources/js/search/search_box.ts b/ash/webui/shortcut_customization_ui/resources/js/search/search_box.ts
index 86f0e30c..72a38f1 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/search/search_box.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/search/search_box.ts
@@ -4,6 +4,8 @@
 
 import 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import './search_result_row.js';
+import 'chrome://resources/polymer/v3_0/iron-dropdown/iron-dropdown.js';
+import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
 
 import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
 import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
@@ -50,11 +52,24 @@
         value: false,
         reflectToAttribute: true,
       },
+
+      searchResultsExist: {
+        type: Boolean,
+        value: false,
+        computed: 'computeSearchResultsExist(searchResults)',
+      },
+
+      hasSearchQuery: {
+        type: Boolean,
+        value: false,
+        reflectToAttribute: true,
+      },
     };
   }
 
   searchResults: MojoSearchResult[];
   shouldShowDropdown: boolean;
+  hasSearchQuery: true;
   private shortcutSearchHandler: ShortcutSearchHandlerInterface;
 
   constructor() {
@@ -62,10 +77,26 @@
     this.shortcutSearchHandler = getShortcutSearchHandler();
   }
 
+  override ready(): void {
+    super.ready();
+
+    this.addEventListener('blur', this.onBlur);
+  }
+
+  private onBlur(event: UIEvent): void {
+    event.stopPropagation();
+    // Close the dropdown because a region outside the search box was clicked.
+    this.shouldShowDropdown = false;
+  }
+
+  private computeSearchResultsExist(): boolean {
+    return this.searchResults.length !== 0;
+  }
+
   private getCurrentQuery(): string {
-    const searchField =
-        strictQuery('#search', this.shadowRoot, CrToolbarSearchFieldElement);
-    return searchField.getSearchInput().value;
+    return strictQuery('#search', this.shadowRoot, CrToolbarSearchFieldElement)
+        .getSearchInput()
+        .value;
   }
 
   // TODO(longbowei): Query the search results as user is typing. Add some
@@ -74,11 +105,12 @@
     if (e.key === 'Enter') {
       this.shouldShowDropdown = true;
       const query: string = this.getCurrentQuery();
-      this.getSearchResult(query);
+      this.hasSearchQuery = true;
+      this.fetchSearchResults(query);
     }
   }
 
-  protected getSearchResult(query: string): void {
+  protected fetchSearchResults(query: string): void {
     this.shortcutSearchHandler
         .search(stringToMojoString16(query), MAX_NUM_RESULTS)
         .then((result) => {
diff --git a/ash/webui/shortcut_customization_ui/resources/js/search/search_result_row.html b/ash/webui/shortcut_customization_ui/resources/js/search/search_result_row.html
index dbe07a21..7eeb376 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/search/search_result_row.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/search/search_result_row.html
@@ -1,3 +1,21 @@
+<style>
+  :host {
+    width: 100%;
+  }
+
+  #searchResultRow {
+    align-items: center;
+    display: flex;
+    height: 48px;
+    justify-content: center;
+    margin-inline-start: 24px;
+  }
+
+  #searchResultText {
+    flex-grow: 1;
+  }
+</style>
+
 <div id="searchResultRow">
-  <li id="searchResultText">[[getSearchResultText(searchResult)]]</li>
+  <div id="searchResultText">[[getSearchResultText(searchResult)]]</div>
 </div>
diff --git a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
index 06b421b6..6c417c72 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
+++ b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.html
@@ -45,6 +45,11 @@
   #searchBoxWrapper {
     font-size: var(--shortcuts-font-size-default);
     padding-inline: var(--container-padding-nav);
+    padding-top: 8px;
+  }
+
+  :host search-box {
+    --cr-toolbar-field-width: 680px;
   }
 </style>
 <navigation-view-panel id="navigationPanel" title="[[i18n('appTitle')]]"
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index a354fbf..96865e4 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -17,10 +17,10 @@
 #include "ash/wm/desks/desk_action_view.h"
 #include "ash/wm/desks/desk_name_view.h"
 #include "ash/wm/desks/desk_preview_view.h"
+#include "ash/wm/desks/desk_textfield.h"
 #include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_restore_util.h"
-#include "ash/wm/desks/desks_textfield.h"
 #include "ash/wm/float/float_controller.h"
 #include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_grid.h"
@@ -134,7 +134,7 @@
             IsPointOnMiniView(
                 owner_bar_->last_dragged_item_screen_location())) ||
            desk_preview_->IsViewHighlighted() ||
-           (desk_ && desk_->is_active() &&
+           (desk_ && desk_->is_active() && owner_bar_->overview_grid() &&
             !owner_bar_->overview_grid()->IsShowingSavedDeskLibrary());
   });
 
@@ -233,7 +233,7 @@
        IsPointOnMiniView(owner_bar_->last_dragged_item_screen_location())) ||
       desk_preview_->IsViewHighlighted()) {
     new_focus_color_id = ui::kColorAshFocusRing;
-  } else if (desk_->is_active() &&
+  } else if (desk_->is_active() && owner_bar_->overview_grid() &&
              !owner_bar_->overview_grid()->IsShowingSavedDeskLibrary()) {
     new_focus_color_id = kColorAshCurrentDeskColor;
   } else {
@@ -422,9 +422,9 @@
   // To avoid potential security and memory issues, we don't allow desk names to
   // have an unbounded length. Therefore we trim if needed at kMaxLength UTF-16
   // boundary. Note that we don't care about code point boundaries in this case.
-  if (new_contents.size() > DesksTextfield::kMaxLength) {
+  if (new_contents.size() > DeskTextfield::kMaxLength) {
     std::u16string trimmed_new_contents = new_contents;
-    trimmed_new_contents.resize(DesksTextfield::kMaxLength);
+    trimmed_new_contents.resize(DeskTextfield::kMaxLength);
     desk_name_view_->SetText(trimmed_new_contents);
   }
 
diff --git a/ash/wm/desks/desk_name_view.cc b/ash/wm/desks/desk_name_view.cc
index 16036759..0b61c96 100644
--- a/ash/wm/desks/desk_name_view.cc
+++ b/ash/wm/desks/desk_name_view.cc
@@ -45,11 +45,11 @@
             IDS_ASH_DESKS_NAME_HIGHLIGHT_NOTIFICATION));
   }
 
-  DesksTextfield::OnViewHighlighted();
+  DeskTextfield::OnViewHighlighted();
   mini_view_->owner_bar()->ScrollToShowMiniViewIfNecessary(mini_view_);
 }
 
-BEGIN_METADATA(DeskNameView, DesksTextfield)
+BEGIN_METADATA(DeskNameView, DeskTextfield)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/wm/desks/desk_name_view.h b/ash/wm/desks/desk_name_view.h
index da81a30..95e7c49 100644
--- a/ash/wm/desks/desk_name_view.h
+++ b/ash/wm/desks/desk_name_view.h
@@ -6,7 +6,7 @@
 #define ASH_WM_DESKS_DESK_NAME_VIEW_H_
 
 #include "ash/ash_export.h"
-#include "ash/wm/desks/desks_textfield.h"
+#include "ash/wm/desks/desk_textfield.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
 namespace ash {
@@ -17,7 +17,7 @@
 // the name of its corresponding desk.
 // When Bento is enabled and the user creates a new desk, the accessible name
 // for `this` will be the default desk name.
-class ASH_EXPORT DeskNameView : public DesksTextfield {
+class ASH_EXPORT DeskNameView : public DeskTextfield {
  public:
   METADATA_HEADER(DeskNameView);
 
@@ -26,7 +26,7 @@
   DeskNameView& operator=(const DeskNameView&) = delete;
   ~DeskNameView() override;
 
-  // DesksTextfield:
+  // DeskTextfield:
   void OnViewHighlighted() override;
 
  private:
@@ -34,7 +34,7 @@
   DeskMiniView* const mini_view_;
 };
 
-BEGIN_VIEW_BUILDER(/* no export */, DeskNameView, DesksTextfield)
+BEGIN_VIEW_BUILDER(/* no export */, DeskNameView, DeskTextfield)
 END_VIEW_BUILDER
 
 }  // namespace ash
diff --git a/ash/wm/desks/desks_textfield.cc b/ash/wm/desks/desk_textfield.cc
similarity index 66%
rename from ash/wm/desks/desks_textfield.cc
rename to ash/wm/desks/desk_textfield.cc
index 25e8aed..df124e1 100644
--- a/ash/wm/desks/desks_textfield.cc
+++ b/ash/wm/desks/desk_textfield.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 "ash/wm/desks/desks_textfield.h"
+#include "ash/wm/desks/desk_textfield.h"
 
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
@@ -26,35 +26,14 @@
 namespace {
 
 // The border radius on the text field.
-constexpr int kDesksTextfieldBorderRadius = 4;
+constexpr int kDeskTextfieldBorderRadius = 4;
 
-constexpr int kDesksTextfieldMinHeight = 16;
-
-#if DCHECK_IS_ON()
-bool IsDesksBarOrSavedDeskLibraryWidget(const views::Widget* widget) {
-  if (!widget)
-    return false;
-
-  auto* overview_controller = Shell::Get()->overview_controller();
-  if (!overview_controller->InOverviewSession())
-    return false;
-
-  auto* session = overview_controller->overview_session();
-  for (const auto& grid : session->grid_list()) {
-    if (widget == grid->saved_desk_library_widget() ||
-        widget == grid->desks_widget()) {
-      return true;
-    }
-  }
-
-  return false;
-}
-#endif  // DCHECK_IS_ON()
+constexpr int kDeskTextfieldMinHeight = 16;
 
 }  // namespace
 
-DesksTextfield::DesksTextfield() {
-  views::Builder<DesksTextfield>(this)
+DeskTextfield::DeskTextfield() {
+  views::Builder<DeskTextfield>(this)
       .SetBorder(nullptr)
       .SetCursorEnabled(true)
       .BuildChildren();
@@ -62,7 +41,7 @@
   views::FocusRing* focus_ring =
       StyleUtil::SetUpFocusRingForView(this, kFocusRingHaloInset);
   focus_ring->SetHasFocusPredicate([](views::View* view) {
-    return static_cast<DesksTextfield*>(view)->IsViewHighlighted() ||
+    return static_cast<DeskTextfield*>(view)->IsViewHighlighted() ||
            view->HasFocus();
   });
   focus_ring->SetColorId(ui::kColorAshFocusRing);
@@ -70,17 +49,13 @@
   GetRenderText()->SetElideBehavior(gfx::ELIDE_TAIL);
 }
 
-DesksTextfield::~DesksTextfield() = default;
+DeskTextfield::~DeskTextfield() = default;
 
 // static
-constexpr size_t DesksTextfield::kMaxLength;
+constexpr size_t DeskTextfield::kMaxLength;
 
 // static
-void DesksTextfield::CommitChanges(views::Widget* widget) {
-#if DCHECK_IS_ON()
-  DCHECK(IsDesksBarOrSavedDeskLibraryWidget(widget));
-#endif  // DCHECK_IS_ON()
-
+void DeskTextfield::CommitChanges(views::Widget* widget) {
   auto* focus_manager = widget->GetFocusManager();
   focus_manager->ClearFocus();
   // Avoid having the focus restored to the same view when the parent view is
@@ -88,7 +63,7 @@
   focus_manager->SetStoredFocusView(nullptr);
 }
 
-gfx::Size DesksTextfield::CalculatePreferredSize() const {
+gfx::Size DeskTextfield::CalculatePreferredSize() const {
   const std::u16string& text = GetText();
   int width = 0;
   int height = 0;
@@ -97,17 +72,17 @@
   gfx::Size size{width + GetCaretBounds().width(), height};
   const auto insets = GetInsets();
   size.Enlarge(insets.width(), insets.height());
-  size.SetToMax(gfx::Size(0, kDesksTextfieldMinHeight));
+  size.SetToMax(gfx::Size(0, kDeskTextfieldMinHeight));
   return size;
 }
 
-void DesksTextfield::SetBorder(std::unique_ptr<views::Border> b) {
+void DeskTextfield::SetBorder(std::unique_ptr<views::Border> b) {
   // `views::Textfield` override of `SetBorder()` removes an installed focus
   // ring, which we want to keep.
   views::View::SetBorder(std::move(b));
 }
 
-bool DesksTextfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
+bool DeskTextfield::SkipDefaultKeyEventProcessing(const ui::KeyEvent& event) {
   // The default behavior of the tab key is that it moves the focus to the next
   // available view.
   // We want that to be handled by OverviewHighlightController as part of moving
@@ -115,27 +90,27 @@
   return event.key_code() == ui::VKEY_TAB;
 }
 
-std::u16string DesksTextfield::GetTooltipText(const gfx::Point& p) const {
+std::u16string DeskTextfield::GetTooltipText(const gfx::Point& p) const {
   return GetPreferredSize().width() > width() ? GetText() : std::u16string();
 }
 
-void DesksTextfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+void DeskTextfield::GetAccessibleNodeData(ui::AXNodeData* node_data) {
   Textfield::GetAccessibleNodeData(node_data);
   node_data->SetNameChecked(GetAccessibleName());
 }
 
-void DesksTextfield::OnMouseEntered(const ui::MouseEvent& event) {
+void DeskTextfield::OnMouseEntered(const ui::MouseEvent& event) {
   UpdateViewAppearance();
 }
 
-void DesksTextfield::OnMouseExited(const ui::MouseEvent& event) {
+void DeskTextfield::OnMouseExited(const ui::MouseEvent& event) {
   UpdateViewAppearance();
 }
 
-void DesksTextfield::OnThemeChanged() {
+void DeskTextfield::OnThemeChanged() {
   Textfield::OnThemeChanged();
-  SetBackground(views::CreateRoundedRectBackground(
-      GetBackgroundColor(), kDesksTextfieldBorderRadius));
+  SetBackground(views::CreateRoundedRectBackground(GetBackgroundColor(),
+                                                   kDeskTextfieldBorderRadius));
   AshColorProvider* color_provider = AshColorProvider::Get();
   const SkColor text_color = color_provider->GetContentLayerColor(
       AshColorProvider::ContentLayerType::kTextColorPrimary);
@@ -149,17 +124,17 @@
   UpdateFocusRingState();
 }
 
-ui::Cursor DesksTextfield::GetCursor(const ui::MouseEvent& event) {
+ui::Cursor DeskTextfield::GetCursor(const ui::MouseEvent& event) {
   return ui::mojom::CursorType::kIBeam;
 }
 
-void DesksTextfield::OnFocus() {
+void DeskTextfield::OnFocus() {
   GetRenderText()->SetElideBehavior(gfx::NO_ELIDE);
   views::Textfield::OnFocus();
   UpdateViewAppearance();
 }
 
-void DesksTextfield::OnBlur() {
+void DeskTextfield::OnBlur() {
   GetRenderText()->SetElideBehavior(gfx::ELIDE_TAIL);
   views::Textfield::OnBlur();
   UpdateViewAppearance();
@@ -173,49 +148,50 @@
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(
                      [](base::WeakPtr<views::Widget> w) {
-                       if (w)
+                       if (w) {
                          w->GetFocusManager()->SetStoredFocusView(nullptr);
+                       }
                      },
                      GetWidget()->GetWeakPtr()));
 }
 
-void DesksTextfield::OnDragEntered(const ui::DropTargetEvent& event) {
+void DeskTextfield::OnDragEntered(const ui::DropTargetEvent& event) {
   GetRenderText()->SetElideBehavior(gfx::NO_ELIDE);
   views::Textfield::OnDragEntered(event);
 }
 
-void DesksTextfield::OnDragExited() {
+void DeskTextfield::OnDragExited() {
   GetRenderText()->SetElideBehavior(gfx::ELIDE_TAIL);
   views::Textfield::OnDragExited();
 }
 
-views::View* DesksTextfield::GetView() {
+views::View* DeskTextfield::GetView() {
   return this;
 }
 
-void DesksTextfield::MaybeActivateHighlightedView() {
+void DeskTextfield::MaybeActivateHighlightedView() {
   RequestFocus();
 }
 
-void DesksTextfield::MaybeCloseHighlightedView(bool primary_action) {}
+void DeskTextfield::MaybeCloseHighlightedView(bool primary_action) {}
 
-void DesksTextfield::MaybeSwapHighlightedView(bool right) {}
+void DeskTextfield::MaybeSwapHighlightedView(bool right) {}
 
-void DesksTextfield::OnViewHighlighted() {
+void DeskTextfield::OnViewHighlighted() {
   UpdateFocusRingState();
 }
 
-void DesksTextfield::OnViewUnhighlighted() {
+void DeskTextfield::OnViewUnhighlighted() {
   UpdateFocusRingState();
 }
 
-void DesksTextfield::UpdateFocusRingState() {
+void DeskTextfield::UpdateFocusRingState() {
   views::FocusRing* focus_ring = views::FocusRing::Get(this);
   DCHECK(focus_ring);
   focus_ring->SchedulePaint();
 }
 
-void DesksTextfield::UpdateViewAppearance() {
+void DeskTextfield::UpdateViewAppearance() {
   background()->SetNativeControlColor(GetBackgroundColor());
   // Paint the whole view to update the background. The `SchedulePaint` in
   // `UpdateFocusRingState` will only repaint the focus ring.
@@ -223,10 +199,11 @@
   UpdateFocusRingState();
 }
 
-SkColor DesksTextfield::GetBackgroundColor() const {
+SkColor DeskTextfield::GetBackgroundColor() const {
   // Admin desk templates may be read only.
-  if (GetReadOnly())
+  if (GetReadOnly()) {
     return SK_ColorTRANSPARENT;
+  }
 
   return HasFocus() || IsMouseHovered()
              ? AshColorProvider::Get()->GetControlsLayerColor(
@@ -235,7 +212,7 @@
              : SK_ColorTRANSPARENT;
 }
 
-BEGIN_METADATA(DesksTextfield, views::Textfield)
+BEGIN_METADATA(DeskTextfield, views::Textfield)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/wm/desks/desks_textfield.h b/ash/wm/desks/desk_textfield.h
similarity index 80%
rename from ash/wm/desks/desks_textfield.h
rename to ash/wm/desks/desk_textfield.h
index a56ac50..fb6aa14 100644
--- a/ash/wm/desks/desks_textfield.h
+++ b/ash/wm/desks/desk_textfield.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef ASH_WM_DESKS_DESKS_TEXTFIELD_H_
-#define ASH_WM_DESKS_DESKS_TEXTFIELD_H_
+#ifndef ASH_WM_DESKS_DESK_TEXTFIELD_H_
+#define ASH_WM_DESKS_DESK_TEXTFIELD_H_
 
 #include "ash/ash_export.h"
 #include "ash/wm/overview/overview_highlightable_view.h"
@@ -16,15 +16,15 @@
 // label. It can be highlighted and activated by the
 // `OverviewHighlightController`.
 // TODO(minch): Unify this to ash/style.
-class ASH_EXPORT DesksTextfield : public views::Textfield,
-                                  public OverviewHighlightableView {
+class ASH_EXPORT DeskTextfield : public views::Textfield,
+                                 public OverviewHighlightableView {
  public:
-  METADATA_HEADER(DesksTextfield);
+  METADATA_HEADER(DeskTextfield);
 
-  DesksTextfield();
-  DesksTextfield(const DesksTextfield&) = delete;
-  DesksTextfield& operator=(const DesksTextfield&) = delete;
-  ~DesksTextfield() override;
+  DeskTextfield();
+  DeskTextfield(const DeskTextfield&) = delete;
+  DeskTextfield& operator=(const DeskTextfield&) = delete;
+  ~DeskTextfield() override;
 
   // The max number of characters (UTF-16) allowed for the textfield.
   static constexpr size_t kMaxLength = 300;
@@ -70,11 +70,11 @@
   SkColor GetBackgroundColor() const;
 };
 
-BEGIN_VIEW_BUILDER(/* no export */, DesksTextfield, views::Textfield)
+BEGIN_VIEW_BUILDER(/* no export */, DeskTextfield, views::Textfield)
 END_VIEW_BUILDER
 
 }  // namespace ash
 
-DEFINE_VIEW_BUILDER(/* no export */, ash::DesksTextfield)
+DEFINE_VIEW_BUILDER(/* no export */, ash::DeskTextfield)
 
-#endif  // ASH_WM_DESKS_DESKS_TEXTFIELD_H_
+#endif  // ASH_WM_DESKS_DESK_TEXTFIELD_H_
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index ab619810..192be8a 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -957,6 +957,10 @@
   if (is_bounds_animation_on_going_)
     return;
 
+  if (!overview_grid_) {
+    return;
+  }
+
   // Scroll buttons are kept |kScrollViewMinimumHorizontalPadding| away from
   // the edge of the scroll view. So the horizontal padding of the scroll view
   // is set to guarantee enough space for the scroll buttons.
diff --git a/ash/wm/desks/desks_bar_view.h b/ash/wm/desks/desks_bar_view.h
index 77a274b..4d3cb83 100644
--- a/ash/wm/desks/desks_bar_view.h
+++ b/ash/wm/desks/desks_bar_view.h
@@ -117,6 +117,9 @@
   bool dragged_item_over_bar() const { return dragged_item_over_bar_; }
 
   OverviewGrid* overview_grid() const { return overview_grid_; }
+  void set_overview_grid(OverviewGrid* overview_grid) {
+    overview_grid_ = overview_grid;
+  }
 
   // Initializes and creates mini_views for any pre-existing desks, before the
   // bar was created. This should only be called after this view has been added
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index afafc2fc2..e96f36d 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -40,6 +40,7 @@
 #include "ash/style/color_util.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/ash_test_util.h"
+#include "ash/test/test_widget_builder.h"
 #include "ash/wm/desks/cros_next_default_desk_button.h"
 #include "ash/wm/desks/cros_next_desk_button_base.h"
 #include "ash/wm/desks/cros_next_desk_icon_button.h"
@@ -50,6 +51,7 @@
 #include "ash/wm/desks/desk_mini_view.h"
 #include "ash/wm/desks/desk_name_view.h"
 #include "ash/wm/desks/desk_preview_view.h"
+#include "ash/wm/desks/desk_textfield.h"
 #include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_restore_util.h"
@@ -129,6 +131,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/transform.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/button.h"
@@ -3038,21 +3041,22 @@
   SendKey(ui::VKEY_BACK);
 
   // Simulate user is typing text beyond the max length.
-  std::u16string expected_desk_name(DesksTextfield::kMaxLength, L'a');
-  for (size_t i = 0; i < DesksTextfield::kMaxLength + 10; ++i)
+  std::u16string expected_desk_name(DeskTextfield::kMaxLength, L'a');
+  for (size_t i = 0; i < DeskTextfield::kMaxLength + 10; ++i) {
     SendKey(ui::VKEY_A);
+  }
   SendKey(ui::VKEY_RETURN);
 
   // Desk name has been trimmed.
   auto* desk_1 = controller()->desks()[0].get();
-  EXPECT_EQ(DesksTextfield::kMaxLength, desk_1->name().size());
+  EXPECT_EQ(DeskTextfield::kMaxLength, desk_1->name().size());
   EXPECT_EQ(expected_desk_name, desk_1->name());
   EXPECT_TRUE(desk_1->is_name_set_by_user());
 
   // Test that pasting a large amount of text is trimmed at the max length.
-  std::u16string clipboard_text(DesksTextfield::kMaxLength + 10, L'b');
-  expected_desk_name = std::u16string(DesksTextfield::kMaxLength, L'b');
-  EXPECT_GT(clipboard_text.size(), DesksTextfield::kMaxLength);
+  std::u16string clipboard_text(DeskTextfield::kMaxLength + 10, L'b');
+  expected_desk_name = std::u16string(DeskTextfield::kMaxLength, L'b');
+  EXPECT_GT(clipboard_text.size(), DeskTextfield::kMaxLength);
   ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
       .WriteText(clipboard_text);
 
@@ -3064,7 +3068,7 @@
   // Paste text.
   SendKey(ui::VKEY_V, ui::EF_CONTROL_DOWN);
   SendKey(ui::VKEY_RETURN);
-  EXPECT_EQ(DesksTextfield::kMaxLength, desk_1->name().size());
+  EXPECT_EQ(DeskTextfield::kMaxLength, desk_1->name().size());
   EXPECT_EQ(expected_desk_name, desk_1->name());
 }
 
@@ -6248,6 +6252,23 @@
   EXPECT_EQ(0u, desk_1->GetDeskContainerForRoot(root)->children().size());
 }
 
+// Tests that the desk bar exit animation would not cause any crash or UAF.
+// Please refer to b/274497402.
+TEST_P(DesksTest, DesksBarExitAnimation) {
+  // Create another desk so that the desk bar is not zero state.
+  NewDesk();
+
+  // Set to non-zero animation.
+  ui::ScopedAnimationDurationScaleMode animation(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  // Enter then exit overview. When shutting down overview grid, the desk bar
+  // slide animation will take over the ownership of the desk bar widget. This
+  // is to ensure no crash or UAF after the ownership change.
+  EnterOverview();
+  ExitOverview();
+}
+
 TEST_P(DesksTest, EnterOverviewWithCorrectDesksBarState) {
   auto* controller = DesksController::Get();
   ASSERT_EQ(1u, controller->desks().size());
@@ -6463,8 +6484,9 @@
 
   // Set a super long desk name.
   ClickOnView(GetDefaultDeskButton(desks_bar_view), event_generator);
-  for (size_t i = 0; i < DesksTextfield::kMaxLength + 5; i++)
+  for (size_t i = 0; i < DeskTextfield::kMaxLength + 5; i++) {
     SendKey(ui::VKEY_A);
+  }
   SendKey(ui::VKEY_RETURN);
   ExitOverview();
   EnterOverview();
@@ -6472,7 +6494,7 @@
   desks_bar_view = GetOverviewGridForRoot(root_window)->desks_bar_view();
   auto* zero_state_default_desk_button = GetDefaultDeskButton(desks_bar_view);
   std::u16string desk_button_text = zero_state_default_desk_button->GetText();
-  std::u16string expected_desk_name(DesksTextfield::kMaxLength, L'a');
+  std::u16string expected_desk_name(DeskTextfield::kMaxLength, L'a');
   // Zero state desk button should show the elided name as the DeskNameView.
   EXPECT_EQ(expected_desk_name,
             DesksController::Get()->desks()[0].get()->name());
@@ -9194,6 +9216,26 @@
   EXPECT_FALSE(window.is_valid());
 }
 
+using BentoButtonTest = DesksTest;
+
+// Tests that `DeskTextfield` can be used outside overview.
+TEST_P(BentoButtonTest, DeskTextfieldOutsideOverview) {
+  auto widget =
+      TestWidgetBuilder()
+          .SetDelegate(nullptr)
+          .SetBounds(gfx::Rect(0, 0, 300, 300))
+          .SetParent(Shell::GetPrimaryRootWindow())
+          .SetShow(true)
+          .SetWidgetType(views::Widget::InitParams::TYPE_WINDOW_FRAMELESS)
+          .BuildOwnsNativeWidget();
+  auto* desk_text_view =
+      widget->SetContentsView(std::make_unique<DeskTextfield>());
+
+  // There is no crash for committing name changes for `DeskTextfield` outside
+  // overview.
+  desk_text_view->CommitChanges(widget.get());
+}
+
 // TODO(afakhry): Add more tests:
 // - Always on top windows are not tracked by any desk.
 // - Reusing containers when desks are removed and created.
@@ -9245,6 +9287,7 @@
 INSTANTIATE_TEST_SUITE_P(All, PersistentDesksBarTest, ValuesIn(kDeskCountOnly));
 INSTANTIATE_TEST_SUITE_P(All, DesksCloseAllTest, ValuesIn(kDeskCountOnly));
 INSTANTIATE_TEST_SUITE_P(All, PerDeskShelfTest, ::testing::Bool());
+INSTANTIATE_TEST_SUITE_P(All, BentoButtonTest, ValuesIn(kAllCombinations));
 
 }  // namespace
 
diff --git a/ash/wm/desks/templates/saved_desk_controller.cc b/ash/wm/desks/templates/saved_desk_controller.cc
index aede3b4..81443d2 100644
--- a/ash/wm/desks/templates/saved_desk_controller.cc
+++ b/ash/wm/desks/templates/saved_desk_controller.cc
@@ -4,19 +4,86 @@
 
 #include "ash/wm/desks/templates/saved_desk_controller.h"
 
+#include "ash/public/cpp/desk_template.h"
+#include "ash/public/cpp/saved_desk_delegate.h"
+#include "ash/shell.h"
+#include "ash/wm/desks/desks_controller.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/app_restore/restore_data.h"
+
 namespace ash {
 
+namespace {
+
+constexpr char kPlaceholderUuid[] = "2a0fe322-c912-468e-bd9c-5e8fddcc1606";
+constexpr char kPlaceholderName[] = "Test template";
+constexpr char kPlaceholderJson[] = R"json(
+{
+   "mgndgikekgjfcpckkfioiadnlibdjbkf": {
+      "1": {
+         "active_tab_index": 0,
+         "app_name": "",
+         "current_bounds": [ 100, 50, 640, 480 ],
+         "display_id": "2200000000",
+         "index": 0,
+         "title": "Chrome",
+         "urls": [ "https://www.google.com/" ],
+         "window_state_type": 0
+      }
+   }
+})json";
+
+// Creates a placeholder template that will be used during development.
+std::unique_ptr<DeskTemplate> CreatePlaceholderTemplate() {
+  auto desk_template = std::make_unique<DeskTemplate>(
+      base::GUID::ParseLowercase(kPlaceholderUuid), DeskTemplateSource::kPolicy,
+      kPlaceholderName, base::Time::Now(), DeskTemplateType::kTemplate);
+
+  // Create restore data from json.
+  base::JSONReader::Result restore_data =
+      base::JSONReader::ReadAndReturnValueWithError(kPlaceholderJson);
+  if (!restore_data.has_value()) {
+    return nullptr;
+  }
+
+  desk_template->set_desk_restore_data(
+      std::make_unique<app_restore::RestoreData>(
+          std::move(restore_data).value()));
+
+  return desk_template;
+}
+
+}  // namespace
+
 SavedDeskController::SavedDeskController() = default;
 
 SavedDeskController::~SavedDeskController() = default;
 
 std::vector<AdminTemplateMetadata>
 SavedDeskController::GetAdminTemplateMetadata() const {
-  return {};
+  return {AdminTemplateMetadata{
+      .uuid = base::GUID::ParseLowercase(kPlaceholderUuid),
+      .name = kPlaceholderName}};
 }
 
 bool SavedDeskController::LaunchAdminTemplate(const base::GUID& template_uuid) {
-  return false;
+  auto placeholder_template = CreatePlaceholderTemplate();
+  if (!placeholder_template || template_uuid != placeholder_template->uuid()) {
+    return false;
+  }
+
+  // Set apps to launch on the current desk.
+  auto* desks_controller = DesksController::Get();
+  const int desk_index =
+      desks_controller->GetDeskIndex(desks_controller->active_desk());
+  placeholder_template->SetDeskIndex(desk_index);
+
+  Shell::Get()->saved_desk_delegate()->LaunchAppsFromSavedDesk(
+      std::move(placeholder_template));
+
+  return true;
 }
 
 }  // namespace ash
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index 72be430a..534aa209 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -14,7 +14,7 @@
 #include "ash/style/icon_button.h"
 #include "ash/style/pill_button.h"
 #include "ash/style/style_util.h"
-#include "ash/wm/desks/desks_textfield.h"
+#include "ash/wm/desks/desk_textfield.h"
 #include "ash/wm/desks/templates/saved_desk_dialog_controller.h"
 #include "ash/wm/desks/templates/saved_desk_grid_view.h"
 #include "ash/wm/desks/templates/saved_desk_icon_container.h"
@@ -254,7 +254,7 @@
   }
 
   // Use a border to create spacing between `name_view_`s background (set in
-  // `DesksTextfield`) and the actual text. Shift the parent by the same amount
+  // `DeskTextfield`) and the actual text. Shift the parent by the same amount
   // so that the text stays aligned with the text in `time_view`. We shift the
   // parent here and not `name_view_` itself otherwise its bounds will be
   // outside the parent bounds and the background will get clipped.
@@ -603,9 +603,9 @@
   // names to have an unbounded length. Therefore we trim if needed at
   // `kMaxLength` UTF-16 boundary. Note that we don't care about code point
   // boundaries in this case.
-  if (new_contents.size() > DesksTextfield::kMaxLength) {
+  if (new_contents.size() > DeskTextfield::kMaxLength) {
     std::u16string trimmed_new_contents = new_contents;
-    trimmed_new_contents.resize(DesksTextfield::kMaxLength);
+    trimmed_new_contents.resize(DeskTextfield::kMaxLength);
     name_view_->SetText(trimmed_new_contents);
   }
 
diff --git a/ash/wm/desks/templates/saved_desk_name_view.cc b/ash/wm/desks/templates/saved_desk_name_view.cc
index b96a81c..12142f7ff 100644
--- a/ash/wm/desks/templates/saved_desk_name_view.cc
+++ b/ash/wm/desks/templates/saved_desk_name_view.cc
@@ -24,7 +24,7 @@
   SetFontList(GetFontList().Derive(kNameFontSizeDeltaDp, gfx::Font::NORMAL,
                                    gfx::Font::Weight::MEDIUM));
 
-  // The focus ring is created in `DesksTextfield`'s constructor.
+  // The focus ring is created in `DeskTextfield`'s constructor.
   views::FocusRing* focus_ring = views::FocusRing::Get(this);
   DCHECK(focus_ring);
   focus_ring->SetHaloInset(-kFocusRingGapDp);
@@ -37,12 +37,12 @@
 }
 
 gfx::Size SavedDeskNameView::CalculatePreferredSize() const {
-  return gfx::Size(DesksTextfield::CalculatePreferredSize().width(),
+  return gfx::Size(DeskTextfield::CalculatePreferredSize().width(),
                    kSavedDeskNameViewHeight);
 }
 
 void SavedDeskNameView::OnGestureEvent(ui::GestureEvent* event) {
-  DesksTextfield::OnGestureEvent(event);
+  DeskTextfield::OnGestureEvent(event);
   // Stop propagating this event so that the parent of `this`, which is a button
   // does not get the event.
   event->StopPropagation();
@@ -53,7 +53,7 @@
   PreferredSizeChanged();
 }
 
-BEGIN_METADATA(SavedDeskNameView, DesksTextfield)
+BEGIN_METADATA(SavedDeskNameView, DeskTextfield)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/wm/desks/templates/saved_desk_name_view.h b/ash/wm/desks/templates/saved_desk_name_view.h
index 30799aa2..e5edd4e8 100644
--- a/ash/wm/desks/templates/saved_desk_name_view.h
+++ b/ash/wm/desks/templates/saved_desk_name_view.h
@@ -7,7 +7,7 @@
 
 #include <string>
 
-#include "ash/wm/desks/desks_textfield.h"
+#include "ash/wm/desks/desk_textfield.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
@@ -15,7 +15,7 @@
 
 // Defines a textfield styled to normally look like a label. Allows modifying
 // the name of its corresponding saved desk.
-class SavedDeskNameView : public DesksTextfield {
+class SavedDeskNameView : public DeskTextfield {
  public:
   METADATA_HEADER(SavedDeskNameView);
 
@@ -40,7 +40,7 @@
   // size of `this`, which invalidates the layout.
   void OnContentsChanged();
 
-  // DesksTextfield:
+  // DeskTextfield:
   gfx::Size CalculatePreferredSize() const override;
   void OnGestureEvent(ui::GestureEvent* event) override;
 
@@ -49,7 +49,7 @@
   absl::optional<std::u16string> temporary_name_;
 };
 
-BEGIN_VIEW_BUILDER(/* no export */, SavedDeskNameView, DesksTextfield)
+BEGIN_VIEW_BUILDER(/* no export */, SavedDeskNameView, DeskTextfield)
 END_VIEW_BUILDER
 
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 486ea2e..c8826db 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -482,19 +482,6 @@
 void OverviewGrid::Shutdown(OverviewEnterExitType exit_type) {
   EndNudge();
 
-  if (chromeos::features::IsJellyrollEnabled() && desks_widget_ &&
-      exit_type != OverviewEnterExitType::kImmediateExit) {
-    // When applying the slide out animation to the `desks_widget_` during
-    // overview grid shutdown phase, we need to make the lifetime of the
-    // `desks_widget_` longer than its owner (overview grid). Thus move the
-    // ownership of `desks_widget_` from the overview grid to
-    // `DesksBarSlideAnimation` which is a self-deleting object, when the
-    // animation is done, it will delete itself and destroy `desks_widget_` as
-    // well.
-    new DesksBarSlideAnimation(std::move(desks_widget_),
-                               desks_bar_view_->IsZeroState());
-  }
-
   SplitViewController::Get(root_window_)->RemoveObserver(this);
   ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this);
   Shell::Get()->wallpaper_controller()->RemoveObserver(this);
@@ -545,6 +532,26 @@
     FadeOutWidgetFromOverview(std::move(no_windows_widget_),
                               OVERVIEW_ANIMATION_RESTORE_WINDOW);
   }
+
+  // After this, the desk bar widget will not be owned by this overview grid
+  // anymore. It's either owned by the slide animation for a short period of
+  // time for the animation, or destroyed right away. When applying the slide
+  // out animation to the `desks_widget_` during overview grid shutdown phase,
+  // we need to make the lifetime of the `desks_widget_` longer than its owner
+  // (overview grid). Thus move the ownership of `desks_widget_` from the
+  // overview grid to `DesksBarSlideAnimation` which is a self-deleting object,
+  // when the animation is done, it will delete itself and destroy
+  // `desks_widget_` as well.
+  if (chromeos::features::IsJellyrollEnabled() && desks_widget_ &&
+      exit_type != OverviewEnterExitType::kImmediateExit) {
+    bool is_zero_state = desks_bar_view_->IsZeroState();
+    desks_bar_view_->set_overview_grid(nullptr);
+    desks_bar_view_ = nullptr;
+    new DesksBarSlideAnimation(std::move(desks_widget_), is_zero_state);
+  } else {
+    desks_bar_view_ = nullptr;
+    desks_widget_.reset();
+  }
 }
 
 void OverviewGrid::PrepareForOverview() {
diff --git a/base/BUILD.gn b/base/BUILD.gn
index b49d877..80f6eaf 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1508,7 +1508,7 @@
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.buildinfo:fuchsia.buildinfo_cpp",
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.hwinfo:fuchsia.hwinfo_cpp",
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.io:fuchsia.io_cpp_hlcpp_conversion",
-      "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media:fuchsia.media_hlcpp",
+      "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.media:fuchsia.media_cpp",
       "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.sys:fuchsia.sys_hlcpp",
       "//third_party/fuchsia-sdk/sdk/pkg/async-default",
       "//third_party/fuchsia-sdk/sdk/pkg/async-loop-cpp",
diff --git a/base/debug/dump_without_crashing.cc b/base/debug/dump_without_crashing.cc
index 5a94d1ee..2cb61cf 100644
--- a/base/debug/dump_without_crashing.cc
+++ b/base/debug/dump_without_crashing.cc
@@ -33,14 +33,30 @@
   return false;
 }
 
+// Map used to store the most recent time a location called
+// ShouldDumpWithoutCrashWithLocation.
+std::map<base::Location, base::TimeTicks>& LocationToTimestampMap() {
+  static base::NoDestructor<std::map<base::Location, base::TimeTicks>>
+      location_to_timestamp;
+  return *location_to_timestamp;
+}
+
+// Map used to store the most recent time a pair of location and
+// unique_identifier called ShouldDumpWithoutCrashWithLocationAndUniqueId.
+std::map<std::pair<base::Location, size_t>, base::TimeTicks>&
+LocationAndUniqueIdentifierToTimestampMap() {
+  static base::NoDestructor<
+      std::map<std::pair<base::Location, size_t>, base::TimeTicks>>
+      location_and_unique_identifier_to_timestamp;
+  return *location_and_unique_identifier_to_timestamp;
+}
+
 // This function takes `location` and `time_between_dumps` as an input
 // and checks if DumpWithoutCrashing() meets the requirements to take the dump
 // or not.
 bool ShouldDumpWithoutCrashWithLocation(const base::Location& location,
                                         base::TimeDelta time_between_dumps) {
-  static base::NoDestructor<std::map<base::Location, base::TimeTicks>>
-      location_to_timestamp;
-  return ShouldDump(*location_to_timestamp, location, time_between_dumps);
+  return ShouldDump(LocationToTimestampMap(), location, time_between_dumps);
 }
 
 // Pair of `location` and `unique_identifier` creates a unique key and checks
@@ -50,11 +66,8 @@
     size_t unique_identifier,
     const base::Location& location,
     base::TimeDelta time_between_dumps) {
-  static base::NoDestructor<
-      std::map<std::pair<base::Location, size_t>, base::TimeTicks>>
-      location_and_unique_identifier_to_timestamp;
   std::pair<base::Location, size_t> key(location, unique_identifier);
-  return ShouldDump(*location_and_unique_identifier_to_timestamp, key,
+  return ShouldDump(LocationAndUniqueIdentifierToTimestampMap(), key,
                     time_between_dumps);
 }
 
@@ -115,5 +128,10 @@
   dump_without_crashing_function_ = function;
 }
 
+void ClearMapsForTesting() {
+  LocationToTimestampMap().clear();
+  LocationAndUniqueIdentifierToTimestampMap().clear();
+}
+
 }  // namespace debug
 }  // namespace base
diff --git a/base/debug/dump_without_crashing.h b/base/debug/dump_without_crashing.h
index 06768b5..d5d014d5 100644
--- a/base/debug/dump_without_crashing.h
+++ b/base/debug/dump_without_crashing.h
@@ -77,6 +77,9 @@
 // previously set function.
 BASE_EXPORT void SetDumpWithoutCrashingFunction(void (CDECL *function)());
 
+// Clear both maps used to throttle calls to DumpWithoutCrashing for testing.
+BASE_EXPORT void ClearMapsForTesting();
+
 }  // namespace debug
 }  // namespace base
 
diff --git a/base/debug/dump_without_crashing_unittest.cc b/base/debug/dump_without_crashing_unittest.cc
index 7cc0c529..96973c2 100644
--- a/base/debug/dump_without_crashing_unittest.cc
+++ b/base/debug/dump_without_crashing_unittest.cc
@@ -27,7 +27,10 @@
     number_of_dump_calls_ = 0;
   }
 
-  void TearDown() override { SetDumpWithoutCrashingFunction(nullptr); }
+  void TearDown() override {
+    SetDumpWithoutCrashingFunction(nullptr);
+    ClearMapsForTesting();
+  }
 
   // Set override.
   ScopedMockClockOverride clock_;
@@ -46,14 +49,7 @@
 
 int DumpWithoutCrashingTest::number_of_dump_calls_ = 0;
 
-// TODO(crbug.com/1295506) This test is flaky on iOS.
-#if BUILDFLAG(IS_IOS)
-#define MAYBE_DumpWithoutCrashingWithLocation \
-  DISABLED_DumpWithoutCrashingWithLocation
-#else
-#define MAYBE_DumpWithoutCrashingWithLocation DumpWithoutCrashingWithLocation
-#endif
-TEST_F(DumpWithoutCrashingTest, MAYBE_DumpWithoutCrashingWithLocation) {
+TEST_F(DumpWithoutCrashingTest, DumpWithoutCrashingWithLocation) {
   EXPECT_EQ(0, DumpWithoutCrashingTest::number_of_dump_calls());
 
   histogram_tester_.ExpectBucketCount("Stability.DumpWithoutCrashingStatus",
@@ -98,16 +94,7 @@
                                       DumpWithoutCrashingStatus::kUploaded, 3);
 }
 
-// TODO(crbug.com/1295506) This test is flaky on iOS.
-#if BUILDFLAG(IS_IOS)
-#define MAYBE_DumpWithoutCrashingWithLocationAndUniqueId \
-  DISABLED_DumpWithoutCrashingWithLocationAndUniqueId
-#else
-#define MAYBE_DumpWithoutCrashingWithLocationAndUniqueId \
-  DumpWithoutCrashingWithLocationAndUniqueId
-#endif
-TEST_F(DumpWithoutCrashingTest,
-       MAYBE_DumpWithoutCrashingWithLocationAndUniqueId) {
+TEST_F(DumpWithoutCrashingTest, DumpWithoutCrashingWithLocationAndUniqueId) {
   EXPECT_EQ(0, DumpWithoutCrashingTest::number_of_dump_calls());
 
   histogram_tester_.ExpectBucketCount("Stability.DumpWithoutCrashingStatus",
@@ -158,4 +145,4 @@
 }
 
 }  // namespace debug
-}  // namespace base
\ No newline at end of file
+}  // namespace base
diff --git a/base/profiler/libunwindstack_unwinder_android.cc b/base/profiler/libunwindstack_unwinder_android.cc
index d072c07..df8168f 100644
--- a/base/profiler/libunwindstack_unwinder_android.cc
+++ b/base/profiler/libunwindstack_unwinder_android.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Elf.h"
+#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Error.h"
 #include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Maps.h"
 #include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Memory.h"
 #include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Regs.h"
@@ -149,38 +150,37 @@
       UnwindValues{unwinder.LastErrorCode(), /*unwinder.warnings()*/ 0,
                    unwinder.ConsumeFrames()};
 
-  // Check the result of either the first or second unwind. If we were
-  // successful transfer from libunwindstack format into base::Unwinder format.
-  if (values.error_code == unwindstack::ERROR_NONE) {
-    // The list of frames provided by Libunwindstack's Unwind() contains the
-    // executing frame. The executing frame is also added by
-    // StackSamplerImpl::WalkStack(). Ignore the frame from the latter to avoid
-    // duplication. In case a java method was being interpreted libunwindstack
-    // adds a dummy frame for it and then writes the corresponding native frame.
-    // In such a scenario we want to prefer the frames produced by
-    // libunwindstack.
-    DCHECK_EQ(stack->size(), 1u);
-    // Since libunwindstack completed unwinding without errors, so the frames
-    // list shouldn't be empty.
-    DCHECK(!values.frames.empty());
-    stack->clear();
-    for (const unwindstack::FrameData& frame : values.frames) {
-      const ModuleCache::Module* module =
-          module_cache()->GetModuleForAddress(frame.pc);
-      if (module == nullptr && frame.map_info != nullptr) {
-        auto module_for_caching =
-            std::make_unique<NonElfModule>(frame.map_info.get());
-        module = module_for_caching.get();
-        module_cache()->AddCustomNativeModule(std::move(module_for_caching));
-      }
-      stack->emplace_back(frame.pc, module, frame.function_name);
-    }
+  if (values.error_code != unwindstack::ERROR_NONE) {
+    TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"),
+                        "TryUnwind Failure", "error", values.error_code,
+                        "warning", values.warnings, "num_frames",
+                        values.frames.size());
+  }
+  if (values.frames.empty()) {
     return UnwindResult::kCompleted;
   }
-  TRACE_EVENT_INSTANT(TRACE_DISABLED_BY_DEFAULT("cpu_profiler.debug"),
-                      "TryUnwind Failure", "error", values.error_code,
-                      "warning", values.warnings, "num_frames",
-                      values.frames.size());
-  return UnwindResult::kAborted;
+
+  // The list of frames provided by Libunwindstack's Unwind() contains the
+  // executing frame. The executing frame is also added by
+  // StackSamplerImpl::WalkStack(). Ignore the frame from the latter to avoid
+  // duplication. In case a java method was being interpreted libunwindstack
+  // adds a dummy frame for it and then writes the corresponding native frame.
+  // In such a scenario we want to prefer the frames produced by
+  // libunwindstack.
+  DCHECK_EQ(stack->size(), 1u);
+  stack->clear();
+
+  for (const unwindstack::FrameData& frame : values.frames) {
+    const ModuleCache::Module* module =
+        module_cache()->GetModuleForAddress(frame.pc);
+    if (module == nullptr && frame.map_info != nullptr) {
+      auto module_for_caching =
+          std::make_unique<NonElfModule>(frame.map_info.get());
+      module = module_for_caching.get();
+      module_cache()->AddCustomNativeModule(std::move(module_for_caching));
+    }
+    stack->emplace_back(frame.pc, module, frame.function_name);
+  }
+  return UnwindResult::kCompleted;
 }
 }  // namespace base
diff --git a/base/profiler/libunwindstack_unwinder_android_unittest.cc b/base/profiler/libunwindstack_unwinder_android_unittest.cc
index fe0cd5f..b99dd41 100644
--- a/base/profiler/libunwindstack_unwinder_android_unittest.cc
+++ b/base/profiler/libunwindstack_unwinder_android_unittest.cc
@@ -169,14 +169,8 @@
                                scenario.GetOuterFunctionAddressRange()});
 }
 
-// TODO(crbug/1384173): investigate whether this test should pass on 32-bit ARM
-#if defined(ARCH_CPU_ARM_FAMILY) && !defined(ARCH_CPU_ARM64)
-#define MAYBE_JavaFunction DISABLED_JavaFunction
-#else
-#define MAYBE_JavaFunction JavaFunction
-#endif
 // Checks that java frames can be unwound through and have function names.
-TEST(LibunwindstackUnwinderAndroidTest, MAYBE_JavaFunction) {
+TEST(LibunwindstackUnwinderAndroidTest, JavaFunction) {
   auto* build_info = base::android::BuildInfo::GetInstance();
   // Due to varying availability of compiled/JITed java unwind tables, unwinding
   // is only expected to reliably succeed on Android P+
@@ -220,13 +214,7 @@
                                     "callWithJavaFunction"});
 }
 
-// TODO(crbug/1384173): Investigate whether this test should pass on 32-bit ARM.
-#if defined(ARCH_CPU_ARM_FAMILY) && !defined(ARCH_CPU_ARM64)
-#define MAYBE_ReparsesMapsOnNewDynamicLibraryLoad DISABLED_ReparsesMapsOnNewDynamicLibraryLoad
-#else
-#define MAYBE_ReparsesMapsOnNewDynamicLibraryLoad ReparsesMapsOnNewDynamicLibraryLoad
-#endif
-TEST(LibunwindstackUnwinderAndroidTest, MAYBE_ReparsesMapsOnNewDynamicLibraryLoad) {
+TEST(LibunwindstackUnwinderAndroidTest, ReparsesMapsOnNewDynamicLibraryLoad) {
   // The current version of /proc/self/maps is used to create
   // memory_regions_map_ object.
   auto unwinder = std::make_unique<LibunwindstackUnwinderAndroid>();
diff --git a/base/threading/platform_thread_fuchsia.cc b/base/threading/platform_thread_fuchsia.cc
index 9d78aee..1d8c04d 100644
--- a/base/threading/platform_thread_fuchsia.cc
+++ b/base/threading/platform_thread_fuchsia.cc
@@ -10,12 +10,12 @@
 
 #include <mutex>
 
-#include <fuchsia/media/cpp/fidl.h>
+#include <fidl/fuchsia.media/cpp/fidl.h>
 #include <lib/fdio/directory.h>
 #include <lib/sys/cpp/component_context.h>
 
+#include "base/fuchsia/fuchsia_component_connect.h"
 #include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/process_context.h"
 #include "base/fuchsia/scheduler.h"
 #include "base/no_destructor.h"
 #include "base/threading/platform_thread_internal_posix.h"
@@ -26,16 +26,14 @@
 
 namespace {
 
-fuchsia::media::ProfileProviderSyncPtr ConnectProfileProvider() {
-  fuchsia::media::ProfileProviderSyncPtr profile_provider;
-  const zx_status_t status = base::ComponentContextForProcess()->svc()->Connect(
-      profile_provider.NewRequest());
-  if (status != ZX_OK) {
-    ZX_LOG(ERROR, status)
-        << "Failed to connect to ProfileProvider! Is "
-           "fuchsia.media.ProfileProvider in the component sandbox?";
+fidl::SyncClient<fuchsia_media::ProfileProvider> ConnectProfileProvider() {
+  auto profile_provider_client_end =
+      base::fuchsia_component::Connect<fuchsia_media::ProfileProvider>();
+  if (profile_provider_client_end.is_error()) {
+    LOG(ERROR) << base::FidlConnectionErrorMessage(profile_provider_client_end);
+    return {};
   }
-  return profile_provider;
+  return fidl::SyncClient(std::move(profile_provider_client_end.value()));
 }
 
 // Sets the current thread to the given scheduling role, optionally including
@@ -49,7 +47,8 @@
   DCHECK_GE(capacity, 0.0);
   DCHECK_LE(capacity, 1.0);
 
-  static const base::NoDestructor<fuchsia::media::ProfileProviderSyncPtr>
+  static const base::NoDestructor<
+      fidl::SyncClient<fuchsia_media::ProfileProvider>>
       profile_provider(ConnectProfileProvider());
 
   zx::thread dup_thread;
@@ -58,11 +57,16 @@
   ZX_CHECK(status == ZX_OK, status) << "zx_object_duplicate";
 
   std::string role_selector{role_name};
-  int64_t out_period, out_capacity;
-  (*profile_provider)
-      ->RegisterHandlerWithCapacity(std::move(dup_thread), role_selector,
-                                    period.ToZxDuration(), capacity,
-                                    &out_period, &out_capacity);
+  auto result = (*profile_provider)
+                    ->RegisterHandlerWithCapacity(
+                        {{.thread_handle = std::move(dup_thread),
+                          .name = role_selector,
+                          .period = period.ToZxDuration(),
+                          .capacity = capacity}});
+  if (result.is_error()) {
+    ZX_DLOG(ERROR, result.error_value().status())
+        << "Failed call to RegisterHandlerWithCapacity";
+  }
 }
 
 }  // namespace
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index 183305cd..796f614d 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -502,8 +502,7 @@
     def individual_device_set_up(device, host_device_tuples):
       def install_apk(dev):
         # Install test APK.
-        with self._ArchiveLogcat(dev, 'install_apk'):
-          self._delegate.Install(dev)
+        self._delegate.Install(dev)
 
       def push_test_data(dev):
         if self._test_instance.use_existing_test_data:
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 452dde53..0bbfbe1 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -265,15 +265,14 @@
         @trace_event.traced
         def install_helper_internal(d, apk_path=None):
           # pylint: disable=unused-argument
-          with self._ArchiveLogcat(d, 'install_apk'):
-            d.Install(
-                apk,
-                modules=modules,
-                fake_modules=fake_modules,
-                permissions=permissions,
-                additional_locales=additional_locales,
-                instant_app=instant_app,
-                force_queryable=self._test_instance.IsApkForceQueryable(apk))
+          d.Install(
+              apk,
+              modules=modules,
+              fake_modules=fake_modules,
+              permissions=permissions,
+              additional_locales=additional_locales,
+              instant_app=instant_app,
+              force_queryable=self._test_instance.IsApkForceQueryable(apk))
 
         return install_helper_internal
 
diff --git a/build/config/rust.gni b/build/config/rust.gni
index 18ca6ef8..6bb5267 100644
--- a/build/config/rust.gni
+++ b/build/config/rust.gni
@@ -15,10 +15,7 @@
   # Whether to allow Rust code to be part of the Chromium *build process*.
   # This can be used to create Rust test binaries, even if the flag below
   # is false.
-  # TODO(crbug.com/1426472): use_clang_coverage
-  enable_rust =
-      is_linux && !is_official_build && !using_sanitizer &&
-      target_cpu != "x86" && !use_clang_coverage && is_clang && !is_castos
+  enable_rust = false
 
   # As we incrementally enable Rust on mainstream builders, we want to enable
   # the toolchain (by switching 'enable_rust' to true) while still disabling
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 9c1dd1a..7f69d723 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-12.20230320.1.1
+12.20230322.3.1
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index d725fc9..df8e07a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -27,7 +27,6 @@
 import android.view.WindowManager;
 
 import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
@@ -2262,10 +2261,7 @@
                 mStartSurfaceSupplier.hasValue() && mStartSurfaceSupplier.get().isHomepageShown();
         final Tab currentTab = isStartSurfaceHomepageShowing ? null : getActivityTab();
         if (currentTab == null) {
-            BackPressManager.record(BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB);
-            MinimizeAppAndCloseTabBackPressHandler.record(MinimizeAppAndCloseTabType.MINIMIZE_APP);
-            assertOnLastBackPress();
-            moveTaskToBack(true);
+            minimizeAppAndCloseTabOnBackPress(null);
             return true;
         }
 
@@ -2313,7 +2309,14 @@
         return false;
     }
 
-    private boolean minimizeAppAndCloseTabOnBackPress(@NonNull Tab currentTab) {
+    private boolean minimizeAppAndCloseTabOnBackPress(@Nullable Tab currentTab) {
+        if (currentTab == null) {
+            BackPressManager.record(BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB);
+            MinimizeAppAndCloseTabBackPressHandler.record(MinimizeAppAndCloseTabType.MINIMIZE_APP);
+            assertOnLastBackPress();
+            moveTaskToBack(true);
+            return true;
+        }
         final boolean shouldCloseTab = backShouldCloseTab(currentTab);
         final WebContents webContents = currentTab.getWebContents();
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index fa97ab7..5159211f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -13493,8 +13493,8 @@
       <message name="IDS_WEBAUTHN_TRANSPORT_AOA" desc="Menu item text. The user selects this to use their phone, once physically connected with a USB cable, to sign in to a web site on their computer. This appears next to the similar string 'Your phone' with TC ID 4865460888230929643.">
         Your phone with a USB cable
       </message>
-      <message name="IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN" desc="Menu item text. The user selects this to use a security key (an external physical device for user authentication) connected to the computer, or a hardware-based authentication mechanism that is built in to the computer, such as a fingerprint reader.">
-        External security key or built-in sensor
+      <message name="IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN" desc="Menu item text. The user selects this to use Windows Hello or a security key (an external physical device for user authentication). Windows Hello is a Microsoft brand and should be translated the same as it is in Windows. If you edit the language code in https://support.microsoft.com/en-us/windows/learn-about-windows-hello-and-set-it-up-dae28983-8242-bb2a-d3d1-87c9d265a5f0 you can see how Microsoft translates it in several languages. (It is often left untranslated.)">
+        Windows Hello or external security key
       </message>
       <message name="IDS_WEBAUTHN_USB_ACTIVATE_DESCRIPTION" desc="Description in the dialog instructing the user to plug in and activate (e.g. press a button on) their USB security key (an external physical device for user authentication).">
         Insert your security key and touch it
diff --git a/chrome/app/generated_resources_grd/IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN.png.sha1
new file mode 100644
index 0000000..a395c2f0
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_TRANSPORT_POPUP_DIFFERENT_AUTHENTICATOR_WIN.png.sha1
@@ -0,0 +1 @@
+d867305bf1297ea13e903833b3b48e3ca0ad7fc9
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 7efd8fe..be5b16c 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -1166,6 +1166,12 @@
   <message name="IDS_SETTINGS_CHROMEVOX_MENU_ENABLE_EVENT_STREAM_LOGGING" desc="In the ChromeVox settings subpage, under developer options the label for the toggle that enables event stream logging.">
     Enable event stream logging
   </message>
+  <message name="IDS_SETTINGS_CHROMEVOX_BRAILLE_WORD_WRAP" desc="In the ChromeVox settings subpage, the label for the toggle that enables wrapping of words if a whole line doesn't fit on a braille display. When this option is enabled, an effort is made to keep the characters of words together on the display. Otherwise, as many characters as possible are put on each braille display line, possible splitting words between lines.">
+    Enable word wrap
+  </message>
+  <message name="IDS_SETTINGS_CHROMEVOX_MENU_BRAILLE_COMMANDS" desc="In the ChromeVox settings subpage, the label for the toggle that enables displaying Perkins Brailler commands in the ChromeVox menus.">
+    Show braille commands in the ChromeVox menus
+  </message>
   <message name="IDS_SETTINGS_SCREEN_MAGNIFIER_LABEL" desc="Label for checkbox which enables the fullscreen magnifier">
     Full-screen magnifier
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_BRAILLE_WORD_WRAP.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_BRAILLE_WORD_WRAP.png.sha1
new file mode 100644
index 0000000..3b90113f
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_BRAILLE_WORD_WRAP.png.sha1
@@ -0,0 +1 @@
+bfab78ef2a9a9f892dc397694bbc481ffa7567aa
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_MENU_BRAILLE_COMMANDS.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_MENU_BRAILLE_COMMANDS.png.sha1
new file mode 100644
index 0000000..3b90113f
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CHROMEVOX_MENU_BRAILLE_COMMANDS.png.sha1
@@ -0,0 +1 @@
+bfab78ef2a9a9f892dc397694bbc481ffa7567aa
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index d17bb8e..717724a0 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2065,7 +2065,6 @@
     "//components/cookie_config",
     "//components/country_codes",
     "//components/crash/core/browser",
-    "//components/creator",
     "//components/crx_file",
     "//components/custom_handlers",
     "//components/device_event_log",
@@ -2878,7 +2877,6 @@
       "content_creation/notes/internal/note_service_factory.h",
       "content_settings/request_desktop_site_web_contents_observer_android.cc",
       "content_settings/request_desktop_site_web_contents_observer_android.h",
-      "creator/android/creator_api_bridge.cc",
       "device_reauth/android/device_authenticator_android.cc",
       "device_reauth/android/device_authenticator_android.h",
       "device_reauth/android/device_authenticator_bridge.h",
@@ -3264,7 +3262,6 @@
       "//chrome/browser/commerce/merchant_viewer/android:jni_headers",
       "//chrome/browser/consent_auditor/android:jni_headers",
       "//chrome/browser/content_creation/notes/internal/android:jni_headers",
-      "//chrome/browser/creator/android:jni_headers",
       "//chrome/browser/device_reauth/android:jni_headers",
       "//chrome/browser/download/internal/android",
       "//chrome/browser/endpoint_fetcher:jni_headers",
@@ -3338,7 +3335,6 @@
       "//components/content_creation/notes/core/templates",
       "//components/content_settings/android",
       "//components/crash/android:crash_android",
-      "//components/creator",
       "//components/device_reauth",
       "//components/embedder_support/android:context_menu",
       "//components/embedder_support/android:simple_factory_key",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 3cc7af8..c87d26bc 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2902,6 +2902,8 @@
 constexpr char kBorealisStorageBallooningInternalName[] =
     "borealis-storage-ballooning";
 constexpr char kVmPerBootShaderCacheName[] = "vm-per-boot-shader-cache";
+constexpr char kClipboardHistoryLongpressInternalName[] =
+    "clipboard-history-longpress";
 constexpr char kClipboardHistoryReorderInternalName[] =
     "clipboard-history-reorder";
 constexpr char kWelcomeScreenInternalName[] = "welcome-screen";
@@ -5902,11 +5904,6 @@
      flag_descriptions::kHandwritingLegacyRecognitionName,
      flag_descriptions::kHandwritingLegacyRecognitionDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kHandwritingLegacyRecognition)},
-    {"handwriting-legacy-recognition-all-lang",
-     flag_descriptions::kHandwritingLegacyRecognitionAllLangName,
-     flag_descriptions::kHandwritingLegacyRecognitionAllLangDescription,
-     kOsCrOS,
-     FEATURE_VALUE_TYPE(ash::features::kHandwritingLegacyRecognitionAllLang)},
     {"handwriting-library-dlc", flag_descriptions::kHandwritingLibraryDlcName,
      flag_descriptions::kHandwritingLibraryDlcDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(ash::features::kHandwritingLibraryDlc)},
@@ -7527,6 +7524,10 @@
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+    {kClipboardHistoryLongpressInternalName,
+     flag_descriptions::kClipboardHistoryLongpressName,
+     flag_descriptions::kClipboardHistoryLongpressDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kClipboardHistoryLongpress)},
     {kClipboardHistoryReorderInternalName,
      flag_descriptions::kClipboardHistoryReorderName,
      flag_descriptions::kClipboardHistoryReorderDescription, kOsCrOS,
@@ -9817,6 +9818,12 @@
          chromeos::features::kExperimentalWebAppStoragePartitionIsolation)},
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    {"enable-desk-button", flag_descriptions::kDeskButtonName,
+     flag_descriptions::kDeskButtonDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(ash::features::kDeskButton)},
+#endif
+
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
@@ -10018,6 +10025,15 @@
             channel != version_info::Channel::UNKNOWN);
   }
 
+  // Only show clipboard history longpress flag if channel is one of
+  // Beta/Dev/Canary/Unknown.
+  if (!strcmp(kClipboardHistoryLongpressInternalName, entry.internal_name)) {
+    return channel != version_info::Channel::BETA &&
+           channel != version_info::Channel::DEV &&
+           channel != version_info::Channel::CANARY &&
+           channel != version_info::Channel::UNKNOWN;
+  }
+
   // Only show clipboard history reorder flag if channel is one of
   // Dev/Canary/Unknown.
   if (!strcmp(kClipboardHistoryReorderInternalName, entry.internal_name)) {
diff --git a/chrome/browser/ash/BUILD.gn b/chrome/browser/ash/BUILD.gn
index abb1125..e934278 100644
--- a/chrome/browser/ash/BUILD.gn
+++ b/chrome/browser/ash/BUILD.gn
@@ -1437,6 +1437,8 @@
     "input_method/input_method_settings.h",
     "input_method/input_method_syncer.cc",
     "input_method/input_method_syncer.h",
+    "input_method/longpress_control_v_suggester.cc",
+    "input_method/longpress_control_v_suggester.h",
     "input_method/longpress_diacritics_suggester.cc",
     "input_method/longpress_diacritics_suggester.h",
     "input_method/longpress_suggester.cc",
@@ -4735,6 +4737,7 @@
     "app_list/app_list_test_util.cc",
     "app_list/app_list_test_util.h",
     "app_list/app_service/app_service_app_model_builder_unittest.cc",
+    "app_list/arc/arc_app_metrics_util_unittest.cc",
     "app_list/arc/arc_app_sync_metrics_helper_unittest.cc",
     "app_list/arc/arc_app_test.cc",
     "app_list/arc/arc_app_test.h",
diff --git a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
index f89dc36..4f0de96f 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
+++ b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.cc
@@ -35,6 +35,7 @@
 #include "base/task/thread_pool.h"
 #include "base/values.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_list_prefs_factory.h"
+#include "chrome/browser/ash/app_list/arc/arc_app_metrics_util.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_scoped_pref_update.h"
 #include "chrome/browser/ash/app_list/arc/arc_app_utils.h"
 #include "chrome/browser/ash/app_list/arc/arc_default_app_list.h"
@@ -502,6 +503,7 @@
   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
   const base::FilePath& base_path = profile->GetPath();
   base_path_ = base_path.AppendASCII(arc::prefs::kArcApps);
+  arc_app_metrics_util_ = std::make_unique<arc::ArcAppMetricsUtil>();
 
   // Once default apps are ready OnDefaultAppsReady is called.
   default_apps_ = std::make_unique<ArcDefaultAppList>(
@@ -1185,6 +1187,10 @@
   }
 }
 
+void ArcAppListPrefs::OnArcSessionStopped(arc::ArcStopReason stop_reason) {
+  arc_app_metrics_util_->reportIncompleteInstalls();
+}
+
 void ArcAppListPrefs::SetDefaultAppsFilterLevel() {
   // There is no a blocklisting mechanism for Android apps. Until there is
   // one, we have no option but to ban all pre-installed apps on Android side.
@@ -2377,7 +2383,10 @@
     return;
 
   apps_installations_.insert(*package_name);
-
+  if (!(sync_service_ && sync_service_->IsPackageSyncing(*package_name)) &&
+      !IsDefaultPackage(*package_name)) {
+    arc_app_metrics_util_->recordAppInstallStartTime(*package_name);
+  }
   for (auto& observer : observer_list_)
     observer.OnInstallationStarted(*package_name);
 }
@@ -2404,7 +2413,7 @@
         reason = InstallationCounterReasonEnum::POLICY;
       }
       UMA_HISTOGRAM_ENUMERATION("Arc.AppInstalledReason", reason);
-
+      arc_app_metrics_util_->maybeReportInstallTimeDelta(result->package_name);
       packages_to_be_added_.insert(result->package_name);
     }
   }
diff --git a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.h b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.h
index 0d1a3a2..64f25e5 100644
--- a/chrome/browser/ash/app_list/arc/arc_app_list_prefs.h
+++ b/chrome/browser/ash/app_list/arc/arc_app_list_prefs.h
@@ -36,6 +36,7 @@
 class Profile;
 
 namespace arc {
+class ArcAppMetricsUtil;
 class ArcPackageSyncableService;
 template <typename InstanceType, typename HostType>
 class ConnectionHolder;
@@ -402,6 +403,7 @@
 
   // arc::ArcSessionManagerObserver:
   void OnArcPlayStoreEnabledChanged(bool enabled) override;
+  void OnArcSessionStopped(arc::ArcStopReason stop_reason) override;
 
   // arc::ArcPolicyBridge::Observer:
   void OnPolicySent(const std::string& policy) override;
@@ -724,6 +726,7 @@
 
   bool is_remove_all_in_progress_ = false;
   base::OnceClosure remove_all_callback_for_testing_;
+  std::unique_ptr<arc::ArcAppMetricsUtil> arc_app_metrics_util_;
 
   base::WeakPtrFactory<ArcAppListPrefs> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ash/app_list/arc/arc_app_metrics_util.cc b/chrome/browser/ash/app_list/arc/arc_app_metrics_util.cc
new file mode 100644
index 0000000..3b5935c
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_metrics_util.cc
@@ -0,0 +1,43 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/arc/arc_app_metrics_util.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+
+namespace {
+
+constexpr char kManualInstallHistogramBase[] = "Arc.AppInstall.Manual.";
+
+}  // namespace
+
+namespace arc {
+
+ArcAppMetricsUtil::ArcAppMetricsUtil() = default;
+
+ArcAppMetricsUtil::~ArcAppMetricsUtil() = default;
+
+void ArcAppMetricsUtil::recordAppInstallStartTime(const std::string& app_name) {
+  install_start_time_map_[app_name] = base::TimeTicks::Now();
+}
+
+void ArcAppMetricsUtil::maybeReportInstallTimeDelta(
+    const std::string& app_name) {
+  if (install_start_time_map_.count(app_name) == 1) {
+    const base::TimeTicks start_time = install_start_time_map_[app_name];
+    const base::TimeDelta time_delta = base::TimeTicks::Now() - start_time;
+    base::UmaHistogramLongTimes(
+        base::StrCat({kManualInstallHistogramBase, "TimeDelta"}), time_delta);
+    install_start_time_map_.erase(app_name);
+  }
+}
+
+void ArcAppMetricsUtil::reportIncompleteInstalls() {
+  base::UmaHistogramExactLinear(
+      base::StrCat({kManualInstallHistogramBase, "NumAppsIncomplete"}),
+      install_start_time_map_.size(), /*exclusive_max=*/51);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/app_list/arc/arc_app_metrics_util.h b/chrome/browser/ash/app_list/arc/arc_app_metrics_util.h
new file mode 100644
index 0000000..37914f4
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_metrics_util.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_ASH_APP_LIST_ARC_ARC_APP_METRICS_UTIL_H_
+#define CHROME_BROWSER_ASH_APP_LIST_ARC_ARC_APP_METRICS_UTIL_H_
+
+#include <map>
+#include <string>
+
+#include "base/time/time.h"
+
+namespace arc {
+
+// Helper class to record metrics for app installs.
+class ArcAppMetricsUtil {
+ public:
+  ArcAppMetricsUtil();
+  ~ArcAppMetricsUtil();
+
+  // Records the install start time for a specific app.
+  void recordAppInstallStartTime(const std::string& app_name);
+
+  // Reports install time delta for an app to UMA.
+  void maybeReportInstallTimeDelta(const std::string& app_name);
+
+  // Reports the number of incomplete app installs to UMA.
+  void reportIncompleteInstalls();
+
+ private:
+  std::map<std::string, base::TimeTicks> install_start_time_map_;
+};
+
+}  // namespace arc
+
+#endif  // CHROME_BROWSER_ASH_APP_LIST_ARC_ARC_APP_METRICS_UTIL_H_
diff --git a/chrome/browser/ash/app_list/arc/arc_app_metrics_util_unittest.cc b/chrome/browser/ash/app_list/arc/arc_app_metrics_util_unittest.cc
new file mode 100644
index 0000000..8a2bbe8
--- /dev/null
+++ b/chrome/browser/ash/app_list/arc/arc_app_metrics_util_unittest.cc
@@ -0,0 +1,93 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/app_list/arc/arc_app_metrics_util.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kPackageName[] = "com.example.app";
+constexpr char kPackageName2[] = "com.example.app2";
+constexpr char kManualInstallTimeDeltaHistogram[] =
+    "Arc.AppInstall.Manual.TimeDelta";
+constexpr char kManualInstallIncompleteHistogram[] =
+    "Arc.AppInstall.Manual.NumAppsIncomplete";
+
+}  // namespace
+
+namespace arc {
+
+class ArcAppMetricsUtilTest : public testing::Test {
+ public:
+  ArcAppMetricsUtilTest(const ArcAppMetricsUtilTest&) = delete;
+  ArcAppMetricsUtilTest& operator=(const ArcAppMetricsUtilTest&) = delete;
+
+ protected:
+  ArcAppMetricsUtilTest() = default;
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::HistogramTester tester_;
+  ArcAppMetricsUtil arc_app_metrics_util_;
+};
+
+TEST_F(ArcAppMetricsUtilTest, DoNotRecordTimeDeltaWhenNoStartExists) {
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName);
+  tester_.ExpectTotalCount(kManualInstallTimeDeltaHistogram, 0);
+}
+
+TEST_F(ArcAppMetricsUtilTest, RecordCorrectTimeDeltaForOnePackage) {
+  base::TimeDelta expected_time = base::Minutes(1);
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName);
+  task_environment_.AdvanceClock(expected_time);
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName);
+  tester_.ExpectUniqueTimeSample(kManualInstallTimeDeltaHistogram,
+                                 expected_time, 1);
+}
+
+TEST_F(ArcAppMetricsUtilTest, RecordCorrectTimeDeltaForTwoPackages) {
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName);
+  task_environment_.AdvanceClock(base::Minutes(1));
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName2);
+  task_environment_.AdvanceClock(base::Minutes(1));
+
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName);
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName2);
+
+  tester_.ExpectTotalCount(kManualInstallTimeDeltaHistogram, 2);
+  tester_.ExpectTimeBucketCount(kManualInstallTimeDeltaHistogram,
+                                base::Minutes(1), 1);
+  tester_.ExpectTimeBucketCount(kManualInstallTimeDeltaHistogram,
+                                base::Minutes(2), 1);
+}
+
+TEST_F(ArcAppMetricsUtilTest, RecordZeroIncompleteInstalls) {
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName);
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName);
+  arc_app_metrics_util_.reportIncompleteInstalls();
+
+  tester_.ExpectUniqueSample(kManualInstallIncompleteHistogram, 0, 1);
+}
+
+TEST_F(ArcAppMetricsUtilTest, RecordOneIncompleteInstall) {
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName);
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName2);
+  arc_app_metrics_util_.maybeReportInstallTimeDelta(kPackageName);
+  arc_app_metrics_util_.reportIncompleteInstalls();
+
+  tester_.ExpectUniqueSample(kManualInstallIncompleteHistogram, 1, 1);
+}
+
+TEST_F(ArcAppMetricsUtilTest, RecordTwoIncompleteInstalls) {
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName);
+  arc_app_metrics_util_.recordAppInstallStartTime(kPackageName2);
+  arc_app_metrics_util_.reportIncompleteInstalls();
+
+  tester_.ExpectUniqueSample(kManualInstallIncompleteHistogram, 2, 1);
+}
+
+}  // namespace arc
diff --git a/chrome/browser/ash/eol_incentive_util.cc b/chrome/browser/ash/eol_incentive_util.cc
index 579c8f2..4702a514 100644
--- a/chrome/browser/ash/eol_incentive_util.cc
+++ b/chrome/browser/ash/eol_incentive_util.cc
@@ -6,6 +6,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/browser_process.h"
@@ -89,4 +90,12 @@
   return EolIncentiveType::kEolPassed;
 }
 
+void RecordShowSourceHistogram(EolIncentiveShowSource source) {
+  base::UmaHistogramEnumeration(kEolIncentiveShowSourceHistogramName, source);
+}
+
+void RecordButtonClicked(EolIncentiveButtonType type) {
+  base::UmaHistogramEnumeration(kEolIncentiveURLButtonClicked, type);
+}
+
 }  // namespace ash::eol_incentive_util
diff --git a/chrome/browser/ash/eol_incentive_util.h b/chrome/browser/ash/eol_incentive_util.h
index e7db8499..8446e902f 100644
--- a/chrome/browser/ash/eol_incentive_util.h
+++ b/chrome/browser/ash/eol_incentive_util.h
@@ -41,6 +41,50 @@
                                         base::Time eol_date,
                                         base::Time now);
 
+// This enum is used to record UMA histogram values and should not be
+// reordered. Please keep in sync with 'EolIncentiveShowSource' in
+// src/tools/metrics/histograms/enums.xml.
+enum class EolIncentiveShowSource {
+  kNotification_Approaching = 0,
+  kNotification_RecentlyPassed = 1,
+  kNotification_Original = 2,
+  kQuickSettings = 3,
+  kSettingsMainPage = 4,
+  kSettingsAboutPage = 5,
+  kMaxValue = kSettingsAboutPage
+};
+
+// The UMA histogram name for the metric which records incentive show source.
+static constexpr char kEolIncentiveShowSourceHistogramName[] =
+    "Ash.EndOfLife.IncentiveShowSource";
+
+// Record the UMA metric for where an end of life incentive was shown.
+void RecordShowSourceHistogram(EolIncentiveShowSource source);
+
+// This enum is used to record UMA histogram values and should not be
+// reordered. Please keep in sync with 'EolIncentiveButtonType' in
+// src/tools/metrics/histograms/enums.xml.
+enum class EolIncentiveButtonType {
+  kNotification_Offer_Approaching = 0,
+  kNotification_NoOffer_Approaching = 1,
+  kNotification_AboutUpdates_Approaching = 2,
+  kNotification_Offer_RecentlyPassed = 3,
+  kNotification_NoOffer_RecentlyPassed = 4,
+  kNotification_AboutUpdates_RecentlyPassed = 5,
+  kNotification_Original_LearnMore = 6,
+  kNotification_Original_Dismiss = 7,
+  kQuickSettings_Offer_RecentlyPassed = 8,
+  kQuickSettings_NoOffer_RecentlyPassed = 9,
+  kQuickSettings_NoOffer_Passed = 10,
+  kMaxValue = kQuickSettings_NoOffer_Passed
+};
+
+// The UMA histogram name for the metric which records incentive button clicks.
+static constexpr char kEolIncentiveURLButtonClicked[] =
+    "Ash.EndOfLife.IncentiveButtonClicked";
+
+void RecordButtonClicked(EolIncentiveButtonType type);
+
 }  // namespace ash::eol_incentive_util
 
 #endif  // CHROME_BROWSER_ASH_EOL_INCENTIVE_UTIL_H_
diff --git a/chrome/browser/ash/eol_notification.cc b/chrome/browser/ash/eol_notification.cc
index 7e072ed2..63d9276 100644
--- a/chrome/browser/ash/eol_notification.cc
+++ b/chrome/browser/ash/eol_notification.cc
@@ -121,7 +121,9 @@
   if (tray_client) {
     tray_client->SetShowEolNotice(
         incentive_type ==
-            ash::eol_incentive_util::EolIncentiveType::kEolPassed ||
+                ash::eol_incentive_util::EolIncentiveType::kEolPassed ||
+            incentive_type ==
+                ash::eol_incentive_util::EolIncentiveType::kEolPassedRecently,
         incentive_type ==
             ash::eol_incentive_util::EolIncentiveType::kEolPassedRecently);
   }
@@ -192,6 +194,9 @@
                   weak_ptr_factory_.GetWeakPtr()))
           .Build(),
       /*metadata=*/nullptr);
+
+  eol_incentive_util::RecordShowSourceHistogram(
+      eol_incentive_util::EolIncentiveShowSource::kNotification_Original);
 }
 
 void EolNotification::Close(bool by_user) {
@@ -214,16 +219,33 @@
 
   if (dismiss_pref_ == prefs::kEolApproachingIncentiveNotificationDismissed ||
       dismiss_pref_ == prefs::kEolPassedFinalIncentiveDismissed) {
+    bool use_offer_url = features::kEolIncentiveParam.Get() !=
+                         features::EolIncentiveParam::kNoOffer;
     switch (*button_index) {
       case kButtonClaim:
         // Open link for eol incentive notification.
         NewWindowDelegate::GetPrimary()->OpenUrl(
-            GURL(features::kEolIncentiveParam.Get() ==
-                         features::EolIncentiveParam::kNoOffer
-                     ? chrome::kEolIncentiveNotificationNoOfferURL
-                     : chrome::kEolIncentiveNotificationOfferURL),
+            GURL(use_offer_url ? chrome::kEolIncentiveNotificationOfferURL
+                               : chrome::kEolIncentiveNotificationNoOfferURL),
             NewWindowDelegate::OpenUrlFrom::kUserInteraction,
             NewWindowDelegate::Disposition::kNewForegroundTab);
+
+        if (dismiss_pref_ ==
+            prefs::kEolApproachingIncentiveNotificationDismissed) {
+          // Record button pressed for eol approaching.
+          eol_incentive_util::RecordButtonClicked(
+              use_offer_url ? eol_incentive_util::EolIncentiveButtonType::
+                                  kNotification_Offer_Approaching
+                            : eol_incentive_util::EolIncentiveButtonType::
+                                  kNotification_NoOffer_Approaching);
+        } else {
+          // Record button pressed for eol recently passed.
+          eol_incentive_util::RecordButtonClicked(
+              use_offer_url ? eol_incentive_util::EolIncentiveButtonType::
+                                  kNotification_Offer_RecentlyPassed
+                            : eol_incentive_util::EolIncentiveButtonType::
+                                  kNotification_NoOffer_RecentlyPassed);
+        }
         break;
       case kButtonAboutUpdates:
         // Open link to learn more about updates.
@@ -231,6 +253,14 @@
             GURL(chrome::kEolNotificationURL),
             NewWindowDelegate::OpenUrlFrom::kUserInteraction,
             NewWindowDelegate::Disposition::kNewForegroundTab);
+
+        eol_incentive_util::RecordButtonClicked(
+            dismiss_pref_ ==
+                    prefs::kEolApproachingIncentiveNotificationDismissed
+                ? eol_incentive_util::EolIncentiveButtonType::
+                      kNotification_AboutUpdates_Approaching
+                : eol_incentive_util::EolIncentiveButtonType::
+                      kNotification_AboutUpdates_RecentlyPassed);
         break;
     }
     profile_->GetPrefs()->SetBoolean(prefs::kEolNotificationDismissed, true);
@@ -244,10 +274,17 @@
         NewWindowDelegate::GetPrimary()->OpenUrl(
             url, NewWindowDelegate::OpenUrlFrom::kUserInteraction,
             NewWindowDelegate::Disposition::kNewForegroundTab);
+
+        eol_incentive_util::RecordButtonClicked(
+            eol_incentive_util::EolIncentiveButtonType::
+                kNotification_Original_LearnMore);
         break;
       }
       case BUTTON_DISMISS:
         CHECK(dismiss_pref_);
+        eol_incentive_util::RecordButtonClicked(
+            eol_incentive_util::EolIncentiveButtonType::
+                kNotification_Original_Dismiss);
         // Set dismiss pref.
         profile_->GetPrefs()->SetBoolean(*dismiss_pref_, true);
         break;
@@ -398,6 +435,17 @@
                   weak_ptr_factory_.GetWeakPtr()))
           .Build(),
       /*metadata=*/nullptr);
+
+  if (incentive_type == eol_incentive_util::EolIncentiveType::kEolApproaching) {
+    // Record approaching eol notification shown.
+    eol_incentive_util::RecordShowSourceHistogram(
+        eol_incentive_util::EolIncentiveShowSource::kNotification_Approaching);
+  } else {
+    // Record recently passed eol notification shown.
+    eol_incentive_util::RecordShowSourceHistogram(
+        eol_incentive_util::EolIncentiveShowSource::
+            kNotification_RecentlyPassed);
+  }
 }
 
 void EolNotification::ResetDismissedPrefs() {
diff --git a/chrome/browser/ash/extensions/file_manager/DEPS b/chrome/browser/ash/extensions/file_manager/DEPS
index f69c88f..94cb2dd 100644
--- a/chrome/browser/ash/extensions/file_manager/DEPS
+++ b/chrome/browser/ash/extensions/file_manager/DEPS
@@ -1,5 +1,5 @@
 specific_include_rules = {
-  "private_api_dialog.cc": [
+  "private_api_dialog.cc" : [
     "+chrome/browser/ui/views/select_file_dialog_extension.h",
   ],
 }
diff --git a/chrome/browser/ash/extensions/file_manager/device_event_router.cc b/chrome/browser/ash/extensions/file_manager/device_event_router.cc
index 42c1e09b4..5022095 100644
--- a/chrome/browser/ash/extensions/file_manager/device_event_router.cc
+++ b/chrome/browser/ash/extensions/file_manager/device_event_router.cc
@@ -74,8 +74,9 @@
 void DeviceEventRouter::OnDiskRemoved(const ash::disks::Disk& disk) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (is_resuming_ || is_starting_up_)
+  if (is_resuming_ || is_starting_up_) {
     return;
+  }
 
   const std::string& device_path = disk.storage_device_path();
   if (!disk.is_read_only() && disk.is_mounted() &&
@@ -202,8 +203,9 @@
   } else {
     const std::map<std::string, DeviceState>::iterator it =
         device_states_.find(device_path);
-    if (it != device_states_.end())
+    if (it != device_states_.end()) {
       device_states_.erase(it);
+    }
   }
 }
 
diff --git a/chrome/browser/ash/extensions/file_manager/event_router.cc b/chrome/browser/ash/extensions/file_manager/event_router.cc
index b921a73..60a1af8b 100644
--- a/chrome/browser/ash/extensions/file_manager/event_router.cc
+++ b/chrome/browser/ash/extensions/file_manager/event_router.cc
@@ -101,8 +101,9 @@
 bool IsRecoveryToolRunning(Profile* profile) {
   extensions::ExtensionPrefs* extension_prefs =
       extensions::ExtensionPrefs::Get(profile);
-  if (!extension_prefs)
+  if (!extension_prefs) {
     return false;
+  }
 
   const std::string kRecoveryToolIds[] = {
       "kkebgepbbgbcmghedmmdfcbdcodlkngh",  // Recovery tool staging
@@ -110,8 +111,9 @@
   };
 
   for (const auto& extension_id : kRecoveryToolIds) {
-    if (extension_prefs->IsExtensionRunning(extension_id))
+    if (extension_prefs->IsExtensionRunning(extension_id)) {
       return true;
+    }
   }
 
   return false;
@@ -232,8 +234,10 @@
     return false;
   }
 
-  if (device_event_router.is_resuming() || device_event_router.is_starting_up())
+  if (device_event_router.is_resuming() ||
+      device_event_router.is_starting_up()) {
     return false;
+  }
 
   // Do not attempt to open File Manager while the login is in progress or
   // the screen is locked or running in kiosk app mode and make sure the file
@@ -246,8 +250,9 @@
   }
 
   // Do not pop-up the File Manager, if the recovery tool is running.
-  if (IsRecoveryToolRunning(profile))
+  if (IsRecoveryToolRunning(profile)) {
     return false;
+  }
 
   // If the disable-default-apps flag is on, the Files app is not opened
   // automatically on device mount not to obstruct the manual test.
@@ -455,12 +460,13 @@
         GetUmaForFileSystemProvider();
     FileSystemProviderMountedTypeMap::iterator sample =
         fsp_sample_map.find(fsp_key);
-    if (sample != fsp_sample_map.end())
+    if (sample != fsp_sample_map.end()) {
       UMA_HISTOGRAM_ENUMERATION(kFileSystemProviderMountedMetricName,
                                 sample->second);
-    else
+    } else {
       UMA_HISTOGRAM_ENUMERATION(kFileSystemProviderMountedMetricName,
                                 FileSystemProviderMountedType::UNKNOWN);
+    }
   }
 }
 
@@ -551,13 +557,15 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   ash::TabletMode* tablet_mode = ash::TabletMode::Get();
-  if (tablet_mode)
+  if (tablet_mode) {
     tablet_mode->RemoveObserver(this);
+  }
 
   auto* intent_helper =
       arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
-  if (intent_helper)
+  if (intent_helper) {
     intent_helper->RemoveObserver(this);
+  }
 
   ash::system::TimezoneSettings::GetInstance()->RemoveObserver(this);
 
@@ -587,8 +595,9 @@
     volume_manager->RemoveObserver(this);
     volume_manager->RemoveObserver(device_event_router_.get());
     auto* io_task_controller = volume_manager->io_task_controller();
-    if (io_task_controller)
+    if (io_task_controller) {
       io_task_controller->RemoveObserver(this);
+    }
   }
 
   chromeos::PowerManagerClient* const power_manager_client =
@@ -691,17 +700,20 @@
 
   auto* intent_helper =
       arc::ArcIntentHelperBridge::GetForBrowserContext(profile_);
-  if (intent_helper)
+  if (intent_helper) {
     intent_helper->AddObserver(this);
+  }
 
   auto* guest_os_share_path =
       guest_os::GuestOsSharePath::GetForProfile(profile_);
-  if (guest_os_share_path)
+  if (guest_os_share_path) {
     guest_os_share_path->AddObserver(this);
+  }
 
   ash::TabletMode* tablet_mode = ash::TabletMode::Get();
-  if (tablet_mode)
+  if (tablet_mode) {
     tablet_mode->AddObserver(this);
+  }
 
   auto* registry = guest_os::GuestOsService::GetForProfile(profile_)
                        ->MountProviderRegistry();
@@ -746,12 +758,14 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   auto iter = file_watchers_.find(local_path);
-  if (iter == file_watchers_.end())
+  if (iter == file_watchers_.end()) {
     return;
+  }
   // Remove the watcher if |local_path| is no longer watched by any extensions.
   iter->second->RemoveListener(listener_origin);
-  if (iter->second->GetListeners().empty())
+  if (iter->second->GetListeners().empty()) {
     file_watchers_.erase(iter);
+  }
 }
 
 void EventRouter::OnWatcherManagerNotification(
@@ -894,8 +908,9 @@
   // happen at shutdown. This should be removed after removing Drive mounting
   // code in addMount. (addMount -> OnFileSystemMounted -> OnVolumeMounted is
   // the only path to come here after Shutdown is called).
-  if (!profile_)
+  if (!profile_) {
     return;
+  }
 
   DispatchMountCompletedEvent(
       file_manager_private::MOUNT_COMPLETED_EVENT_TYPE_MOUNT, error_code,
@@ -1001,8 +1016,9 @@
   std::string file_system_name;
   std::string full_path;
   if (!util::ExtractMountNameFileSystemNameFullPath(
-          path, &mount_name, &file_system_name, &full_path))
+          path, &mount_name, &file_system_name, &full_path)) {
     return;
+  }
 
   const std::string event_name(
       file_manager_private::OnCrostiniChanged::kEventName);
diff --git a/chrome/browser/ash/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/ash/extensions/file_manager/file_manager_private_apitest.cc
index 4611b1d..55f5c1f3 100644
--- a/chrome/browser/ash/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/ash/extensions/file_manager/file_manager_private_apitest.cc
@@ -271,8 +271,9 @@
       int disk_info_index = mp.disk_info_index;
       if (mp.disk_info_index >= 0) {
         EXPECT_GT(std::size(kTestDisks), static_cast<size_t>(disk_info_index));
-        if (static_cast<size_t>(disk_info_index) >= std::size(kTestDisks))
+        if (static_cast<size_t>(disk_info_index) >= std::size(kTestDisks)) {
           return;
+        }
 
         std::unique_ptr<Disk> disk =
             Disk::Builder()
diff --git a/chrome/browser/ash/extensions/file_manager/file_stream_md5_digester.cc b/chrome/browser/ash/extensions/file_manager/file_stream_md5_digester.cc
index 0419cb7..647a8813 100644
--- a/chrome/browser/ash/extensions/file_manager/file_stream_md5_digester.cc
+++ b/chrome/browser/ash/extensions/file_manager/file_stream_md5_digester.cc
@@ -43,8 +43,9 @@
   const int result =
       reader_->Read(buffer_.get(), kMd5DigestBufferSize,
                     base::BindOnce(&FileStreamMd5Digester::OnChunkRead, this));
-  if (result != net::ERR_IO_PENDING)
+  if (result != net::ERR_IO_PENDING) {
     OnChunkRead(result);
+  }
 }
 
 void FileStreamMd5Digester::OnChunkRead(int bytes_read) {
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_dialog.cc b/chrome/browser/ash/extensions/file_manager/private_api_dialog.cc
index 9c98609..b43401c 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_dialog.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_dialog.cc
@@ -223,8 +223,9 @@
   auto* intent_helper = ARC_GET_INSTANCE_FOR_METHOD(
       arc::ArcServiceManager::Get()->arc_bridge_service()->intent_helper(),
       RequestIntentHandlerList);
-  if (!intent_helper)
+  if (!intent_helper) {
     return RespondNow(Error("Can't get ARC intent helper"));
+  }
 
   arc::mojom::IntentInfoPtr intent = arc::mojom::IntentInfo::New();
   intent->action = "android.intent.action.GET_CONTENT";
@@ -287,8 +288,9 @@
   using api::file_manager_private::FileTask;
   std::vector<api::file_manager_private::AndroidApp> results;
   for (const auto& handler : handlers) {
-    if (arc::IsPickerPackageToExclude(handler->package_name))
+    if (arc::IsPickerPackageToExclude(handler->package_name)) {
       continue;
+    }
 
     api::file_manager_private::AndroidApp app;
     app.name = handler->name;
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc b/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
index 17bff015..bcf38df 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_file_system.cc
@@ -131,11 +131,13 @@
                        uint64_t* total_size,
                        uint64_t* remaining_size) {
   int64_t size = base::SysInfo::AmountOfTotalDiskSpace(mount_path);
-  if (size >= 0)
+  if (size >= 0) {
     *total_size = size;
+  }
   size = base::SysInfo::AmountOfFreeDiskSpace(mount_path);
-  if (size >= 0)
+  if (size >= 0) {
     *remaining_size = size;
+  }
 }
 
 // Retrieves the maximum file name length of the file system of |path|.
@@ -370,8 +372,9 @@
   const std::vector<Profile*>& profiles =
       g_browser_process->profile_manager()->GetLoadedProfiles();
   for (auto* profile : profiles) {
-    if (profile->IsOffTheRecord())
+    if (profile->IsOffTheRecord()) {
       continue;
+    }
     storage::FileSystemContext* const context =
         file_manager::util::GetFileSystemContextForSourceURL(profile,
                                                              source_url());
@@ -426,12 +429,15 @@
 ExtensionFunction::ResponseAction FileWatchFunctionBase::Run() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (!render_frame_host() || !render_frame_host()->GetProcess())
+  if (!render_frame_host() || !render_frame_host()->GetProcess()) {
     return RespondNow(Error("Invalid state"));
+  }
 
   // First param is url of a file to watch.
-  if (args().empty() || !args()[0].is_string() || args()[0].GetString().empty())
+  if (args().empty() || !args()[0].is_string() ||
+      args()[0].GetString().empty()) {
     return RespondNow(Error("Empty watch URL"));
+  }
   const std::string& url = args()[0].GetString();
 
   Profile* const profile = Profile::FromBrowserContext(browser_context());
@@ -449,17 +455,20 @@
   // been unmounted.
   if (IsAddWatch()) {
     VolumeManager* const volume_manager = VolumeManager::Get(profile);
-    if (!volume_manager)
+    if (!volume_manager) {
       return RespondNow(Error("Cannot find VolumeManager"));
+    }
 
     const base::WeakPtr<Volume> volume =
         volume_manager->FindVolumeFromPath(file_system_url.path());
-    if (!volume)
+    if (!volume) {
       return RespondNow(
           Error("Cannot find volume *", Redact(file_system_url.path())));
+    }
 
-    if (!volume->watchable())
+    if (!volume->watchable()) {
       return RespondNow(Error("Volume is not watchable"));
+    }
   }
 
   file_manager::EventRouter* const event_router =
@@ -577,13 +586,15 @@
 
   VolumeManager* const volume_manager =
       VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
-  if (!volume_manager)
+  if (!volume_manager) {
     return RespondNow(Error("Cannot find VolumeManager"));
+  }
 
   base::WeakPtr<Volume> volume =
       volume_manager->FindVolumeById(params->volume_id);
-  if (!volume.get())
+  if (!volume.get()) {
     return RespondNow(Error("Cannot find volume with ID *", params->volume_id));
+  }
 
   // For fusebox volumes, get the underlying (aka regular) volume.
   const auto fusebox = base::StringPiece(file_manager::util::kFuseBox);
@@ -813,8 +824,9 @@
   const storage::FileSystemURL file_system_url(
       file_system_context->CrackURLInFirstPartyContext(
           GURL(params->parent_url)));
-  if (!ash::FileSystemBackend::CanHandleURL(file_system_url))
+  if (!ash::FileSystemBackend::CanHandleURL(file_system_url)) {
     return RespondNow(Error("Invalid URL"));
+  }
 
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
@@ -839,13 +851,15 @@
 
   VolumeManager* const volume_manager =
       VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
-  if (!volume_manager)
+  if (!volume_manager) {
     return RespondNow(Error("Cannot find VolumeManager"));
+  }
 
   const base::WeakPtr<Volume> volume =
       volume_manager->FindVolumeById(params->volume_id);
-  if (!volume)
+  if (!volume) {
     return RespondNow(Error("Cannot find volume with ID *", params->volume_id));
+  }
 
   DiskMountManager::GetInstance()->FormatMountedDevice(
       volume->mount_path().AsUTF8Unsafe(),
@@ -897,13 +911,15 @@
 
   VolumeManager* const volume_manager =
       VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
-  if (!volume_manager)
+  if (!volume_manager) {
     return RespondNow(Error("Cannot find VolumeManager"));
+  }
 
   const base::WeakPtr<Volume> volume =
       volume_manager->FindVolumeById(params->volume_id);
-  if (!volume)
+  if (!volume) {
     return RespondNow(Error("Cannot find volume with ID *", params->volume_id));
+  }
 
   DiskMountManager::GetInstance()->RenameMountedDevice(
       volume->mount_path().AsUTF8Unsafe(), params->new_name);
@@ -1372,8 +1388,9 @@
     const base::FilePath& prefix) {
   std::vector<drive::HashAndFilePath> results;
 
-  if (hashes.empty())
+  if (hashes.empty()) {
     return results;
+  }
 
   std::set<std::string> remaining = hashes;
   std::vector<char> attribute;
@@ -1388,8 +1405,9 @@
         bool success = dir.AppendRelativePath(path, &drive_path);
         DCHECK(success);
         results.push_back({md5, drive_path});
-        if (remaining.empty())
+        if (remaining.empty()) {
           break;
+        }
       }
     }
   }
@@ -1562,8 +1580,9 @@
 
   VolumeManager* const volume_manager =
       VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
-  if (!volume_manager || !volume_manager->io_task_controller())
+  if (!volume_manager || !volume_manager->io_task_controller()) {
     return RespondNow(Error("Cannot find VolumeManager"));
+  }
 
   std::vector<storage::FileSystemURL> source_urls;
   for (const std::string& url : params->urls) {
@@ -1682,8 +1701,9 @@
 
   VolumeManager* const volume_manager =
       VolumeManager::Get(Profile::FromBrowserContext(browser_context()));
-  if (!volume_manager || !volume_manager->io_task_controller())
+  if (!volume_manager || !volume_manager->io_task_controller()) {
     return RespondNow(Error("Cannot find VolumeManager"));
+  }
 
   if (params->task_id <= 0) {
     return RespondNow(Error("Invalid task id"));
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_holding_space.cc b/chrome/browser/ash/extensions/file_manager/private_api_holding_space.cc
index 4a53ac9a..0acdb652 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_holding_space.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_holding_space.cc
@@ -37,8 +37,9 @@
   ash::HoldingSpaceKeyedService* const holding_space =
       ash::HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
           browser_context());
-  if (!holding_space)
+  if (!holding_space) {
     return RespondNow(Error("Not enabled"));
+  }
 
   scoped_refptr<storage::FileSystemContext> file_system_context =
       file_manager::util::GetFileSystemContextForRenderFrameHost(
@@ -48,8 +49,9 @@
   for (const auto& item_url : params->urls) {
     const storage::FileSystemURL file_system_url =
         file_system_context->CrackURLInFirstPartyContext(GURL(item_url));
-    if (!file_system_url.is_valid())
+    if (!file_system_url.is_valid()) {
       return RespondNow(Error("Invalid item URL " + item_url));
+    }
     file_system_urls.push_back(file_system_url);
   }
 
@@ -83,14 +85,16 @@
   ash::HoldingSpaceKeyedService* const holding_space =
       ash::HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(
           browser_context());
-  if (!holding_space)
+  if (!holding_space) {
     return RespondNow(Error("Not enabled"));
+  }
 
   std::vector<GURL> items = holding_space->GetPinnedFiles();
 
   api::file_manager_private::HoldingSpaceState holding_space_state;
-  for (const auto& item : items)
+  for (const auto& item : items) {
     holding_space_state.item_urls.push_back(item.spec());
+  }
 
   return RespondNow(ArgumentList(
       api::file_manager_private::GetHoldingSpaceState::Results::Create(
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_media_parser_util.cc b/chrome/browser/ash/extensions/file_manager/private_api_media_parser_util.cc
index 46791e7..12321f9 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_media_parser_util.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_media_parser_util.cc
@@ -17,22 +17,25 @@
 template <class T>
 void SetValueOptional(T value, absl::optional<T>* destination) {
   DCHECK(destination);
-  if (value >= 0)
+  if (value >= 0) {
     *destination = value;
+  }
 }
 
 template <>
 void SetValueOptional(std::string value,
                       absl::optional<std::string>* destination) {
   DCHECK(destination);
-  if (!value.empty())
+  if (!value.empty()) {
     *destination = std::move(value);
+  }
 }
 
 void ChangeAudioMimePrefixToVideo(std::string* mime_type) {
   const std::string audio_type("audio/*");
-  if (net::MatchesMimeType(audio_type, *mime_type))
+  if (net::MatchesMimeType(audio_type, *mime_type)) {
     mime_type->replace(0, audio_type.length() - 1, "video/");
+  }
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_mount.cc b/chrome/browser/ash/extensions/file_manager/private_api_mount.cc
index 16f19a3..2b94afe1 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_mount.cc
@@ -88,8 +88,9 @@
 
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  if (params->password)
+  if (params->password) {
     options_.push_back("password=" + *params->password);
+  }
 
   extension_ = base::ToLowerASCII(path_.Extension());
 
@@ -279,8 +280,9 @@
 
 ExtensionFunction::ResponseAction
 FileManagerPrivateGetVolumeMetadataListFunction::Run() {
-  if (!args().empty())
+  if (!args().empty()) {
     return RespondNow(Error("Invalid arguments"));
+  }
 
   Profile* const profile = Profile::FromBrowserContext(browser_context());
   const std::vector<base::WeakPtr<file_manager::Volume>>& volume_list =
@@ -293,8 +295,9 @@
     file_manager::util::VolumeToVolumeMetadata(profile, *volume,
                                                &volume_metadata);
     result.push_back(std::move(volume_metadata));
-    if (!log_string.empty())
+    if (!log_string.empty()) {
       log_string += ", ";
+    }
     log_string += volume->mount_path().AsUTF8Unsafe();
   }
 
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
index 87c3469..339bbcbb 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_sharesheet.cc
@@ -58,8 +58,9 @@
   const absl::optional<Params> params = Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  if (params->urls.empty())
+  if (params->urls.empty()) {
     return RespondNow(Error("No URLs provided"));
+  }
 
   profile_ = Profile::FromBrowserContext(browser_context());
 
@@ -76,8 +77,9 @@
     if (drive::util::HasHostedDocumentExtension(file_system_url.path())) {
       contains_hosted_document_ = true;
     }
-    if (!ash::FileSystemBackend::CanHandleURL(file_system_url))
+    if (!ash::FileSystemBackend::CanHandleURL(file_system_url)) {
       continue;
+    }
     urls_.push_back(url);
     file_system_urls_.push_back(file_system_url);
   }
@@ -187,8 +189,9 @@
   const absl::optional<Params> params = Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  if (params->urls.empty())
+  if (params->urls.empty()) {
     return RespondNow(Error("No URLs provided"));
+  }
 
   if (params->dlp_source_urls.size() != params->urls.size()) {
     return RespondNow(Error("Mismatching URLs and DLP source URLs provided"));
@@ -206,10 +209,12 @@
     const GURL url(url_string);
     storage::FileSystemURL file_system_url(
         file_system_context->CrackURLInFirstPartyContext(url));
-    if (drive::util::HasHostedDocumentExtension(file_system_url.path()))
+    if (drive::util::HasHostedDocumentExtension(file_system_url.path())) {
       contains_hosted_document_ = true;
-    if (!ash::FileSystemBackend::CanHandleURL(file_system_url))
+    }
+    if (!ash::FileSystemBackend::CanHandleURL(file_system_url)) {
       continue;
+    }
     urls_.push_back(url);
     file_system_urls_.push_back(file_system_url);
   }
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc b/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc
index ba29a39..4500f89 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc
@@ -48,11 +48,13 @@
   for (const auto& file_url : file_urls) {
     const storage::FileSystemURL url =
         context->CrackURLInFirstPartyContext(GURL{file_url});
-    if (!url.is_valid() || url.path().empty())
+    if (!url.is_valid() || url.path().empty()) {
       return {};
+    }
     // We'll skip empty suffixes.
-    if (!url.path().Extension().empty())
+    if (!url.path().Extension().empty()) {
       suffixes.insert(url.path().Extension());
+    }
   }
   return suffixes;
 }
@@ -63,8 +65,9 @@
   std::set<std::string> mime_types;
   for (const auto& mime_type : mime_type_list) {
     // We'll skip empty MIME types and existing MIME types.
-    if (!mime_type.empty())
+    if (!mime_type.empty()) {
       mime_types.insert(mime_type);
+    }
   }
   return mime_types;
 }
@@ -167,8 +170,9 @@
   const absl::optional<Params> params = Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
 
-  if (params->urls.empty())
+  if (params->urls.empty()) {
     return RespondNow(Error("No URLs provided"));
+  }
 
   if (params->dlp_source_urls.size() != params->urls.size()) {
     return RespondNow(Error("Mismatching URLs and DLP source URLs provided"));
@@ -185,8 +189,9 @@
     const GURL url{url_param};
     storage::FileSystemURL file_system_url(
         file_system_context->CrackURLInFirstPartyContext(url));
-    if (!ash::FileSystemBackend::CanHandleURL(file_system_url))
+    if (!ash::FileSystemBackend::CanHandleURL(file_system_url)) {
       continue;
+    }
     urls_.push_back(url);
     local_paths_.push_back(file_system_url.path());
   }
@@ -245,8 +250,9 @@
     converted.descriptor.task_type =
         TaskTypeToString(task.task_descriptor.task_type);
     converted.descriptor.action_id = task.task_descriptor.action_id;
-    if (!task.icon_url.is_empty())
+    if (!task.icon_url.is_empty()) {
       converted.icon_url = task.icon_url.spec();
+    }
     converted.title = task.task_title;
     converted.is_default = task.is_default;
     converted.is_generic_file_handler = task.is_generic_file_handler;
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_util.cc b/chrome/browser/ash/extensions/file_manager/private_api_util.cc
index e2d6f8a..52d4338 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_util.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_util.cc
@@ -626,8 +626,9 @@
   const storage::FileSystemURL filesystem_url(
       file_system_context->CrackURLInFirstPartyContext(url));
   base::FilePath path;
-  if (!ash::FileSystemBackend::CanHandleURL(filesystem_url))
+  if (!ash::FileSystemBackend::CanHandleURL(filesystem_url)) {
     return base::FilePath();
+  }
   return filesystem_url.path();
 }
 
@@ -658,8 +659,9 @@
 }
 
 drive::EventLogger* GetLogger(Profile* profile) {
-  if (!profile)
+  if (!profile) {
     return nullptr;
+  }
   drive::DriveIntegrationService* service =
       drive::DriveIntegrationServiceFactory::FindForProfile(profile);
   return service ? service->event_logger() : nullptr;
diff --git a/chrome/browser/ash/extensions/file_manager/private_api_util.h b/chrome/browser/ash/extensions/file_manager/private_api_util.h
index a2c6f45..7490b17 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_util.h
+++ b/chrome/browser/ash/extensions/file_manager/private_api_util.h
@@ -29,7 +29,7 @@
 namespace base {
 class File;
 class FilePath;
-}
+}  // namespace base
 
 namespace content {
 class RenderFrameHost;
@@ -46,8 +46,8 @@
 struct IconSet;
 struct VolumeMetadata;
 struct MountableGuest;
-}
-}
+}  // namespace file_manager_private
+}  // namespace api
 }  // namespace extensions
 
 namespace ui {
diff --git a/chrome/browser/ash/extensions/file_manager/system_notification_manager_unittest.cc b/chrome/browser/ash/extensions/file_manager/system_notification_manager_unittest.cc
index cc86749..f7857c4 100644
--- a/chrome/browser/ash/extensions/file_manager/system_notification_manager_unittest.cc
+++ b/chrome/browser/ash/extensions/file_manager/system_notification_manager_unittest.cc
@@ -63,8 +63,9 @@
     notification_ids_.insert(notification.id());
     strings.title = notification.title();
     strings.message = notification.message();
-    for (const message_center::ButtonInfo& button : notification.buttons())
+    for (const message_center::ButtonInfo& button : notification.buttons()) {
       strings.buttons.push_back(button.title);
+    }
     notifications_[notification.id()] = strings;
     delegates_[notification.id()] = notification.delegate();
   }
diff --git a/chrome/browser/ash/input_method/assistive_suggester.cc b/chrome/browser/ash/input_method/assistive_suggester.cc
index fa4690de..7bdd71e 100644
--- a/chrome/browser/ash/input_method/assistive_suggester.cc
+++ b/chrome/browser/ash/input_method/assistive_suggester.cc
@@ -4,11 +4,14 @@
 #include "chrome/browser/ash/input_method/assistive_suggester.h"
 #include <string>
 
+#include "ash/clipboard/clipboard_history_controller_impl.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/shell.h"
 #include "base/containers/fixed_flat_set.h"
 #include "base/feature_list.h"
+#include "base/functional/bind.h"
 #include "base/hash/hash.h"
 #include "base/location.h"
 #include "base/metrics/histogram_functions.h"
@@ -27,10 +30,11 @@
 #include "ui/base/ime/ash/ime_bridge.h"
 #include "ui/base/ime/ash/input_method_ukm.h"
 #include "ui/base/ime/ash/text_input_target.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
 #include "url/gurl.h"
 
-namespace ash {
-namespace input_method {
+namespace ash::input_method {
 
 namespace {
 
@@ -38,6 +42,10 @@
 using ime::AssistiveSuggestionMode;
 using ime::AssistiveSuggestionType;
 
+constexpr int kModifierKeysMask = ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
+                                  ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN |
+                                  ui::EF_FUNCTION_DOWN | ui::EF_ALTGR_DOWN;
+
 const char kMaxTextBeforeCursorLength = 50;
 
 constexpr base::TimeDelta kLongpressActivationDelay = base::Milliseconds(500);
@@ -143,6 +151,16 @@
                                 state);
 }
 
+// Returns whether Ctrl+V is pressed with Ctrl+V long-press behavior enabled.
+bool IsLongpressEnabledControlV(const ui::KeyEvent& event) {
+  if (!features::IsClipboardHistoryLongpressEnabled()) {
+    return false;
+  }
+
+  return event.key_code() == ui::VKEY_V &&
+         (event.flags() & kModifierKeysMask) == ui::EF_CONTROL_DOWN;
+}
+
 void RecordMultiWordTextInputState(
     PrefService* pref_service,
     const std::string& engine_id,
@@ -177,6 +195,7 @@
       emoji_suggester_(suggestion_handler, profile),
       multi_word_suggester_(suggestion_handler, profile),
       longpress_diacritics_suggester_(suggestion_handler),
+      longpress_control_v_suggester_(suggestion_handler),
       suggester_switch_(std::move(suggester_switch)) {
   RecordAssistiveUserPrefForEmoji(
       profile_->GetPrefs()->GetBoolean(prefs::kEmojiSuggestionEnabled));
@@ -187,7 +206,8 @@
 bool AssistiveSuggester::IsAssistiveFeatureEnabled() {
   return IsEmojiSuggestAdditionEnabled() || IsMultiWordSuggestEnabled() ||
          IsEnhancedEmojiSuggestEnabled() ||
-         IsDiacriticsOnPhysicalKeyboardLongpressEnabled();
+         IsDiacriticsOnPhysicalKeyboardLongpressEnabled() ||
+         features::IsClipboardHistoryLongpressEnabled();
 }
 
 void AssistiveSuggester::FetchEnabledSuggestionsFromBrowserContextThen(
@@ -311,6 +331,7 @@
   emoji_suggester_.OnFocus(context_id);
   multi_word_suggester_.OnFocus(context_id);
   longpress_diacritics_suggester_.OnFocus(context_id);
+  longpress_control_v_suggester_.OnFocus(context_id);
   enabled_suggestions_from_last_onfocus_ = absl::nullopt;
   suggester_switch_->FetchEnabledSuggestionsThen(
       base::BindOnce(&AssistiveSuggester::HandleEnabledSuggestionsOnFocus,
@@ -329,6 +350,7 @@
   emoji_suggester_.OnBlur();
   multi_word_suggester_.OnBlur();
   longpress_diacritics_suggester_.OnBlur();
+  longpress_control_v_suggester_.OnBlur();
 }
 
 bool AssistiveSuggester::OnKeyEvent(const ui::KeyEvent& event) {
@@ -376,22 +398,26 @@
 
 bool AssistiveSuggester::HandleLongpressEnabledKeyEvent(
     const ui::KeyEvent& event) {
-  if (!IsDiacriticsOnPhysicalKeyboardLongpressEnabled() ||
-      !enabled_suggestions_from_last_onfocus_ ||
-      !enabled_suggestions_from_last_onfocus_->diacritic_suggestions ||
-      !kDefaultLongpressEnabledKeys.contains(event.GetCharacter())) {
+  const bool is_enabled_diacritic_long_press =
+      IsDiacriticsOnPhysicalKeyboardLongpressEnabled() &&
+      enabled_suggestions_from_last_onfocus_ &&
+      enabled_suggestions_from_last_onfocus_->diacritic_suggestions &&
+      kDefaultLongpressEnabledKeys.contains(event.GetCharacter());
+  if (!is_enabled_diacritic_long_press && !IsLongpressEnabledControlV(event)) {
     return false;
   }
 
   // Longpress diacritics behaviour overrides the longpress to repeat key
   // behaviour for alphabetical keys.
-  if (event.is_repeat() &&
-      kDefaultLongpressEnabledKeys.contains(event.GetCharacter())) {
-    // Only emit the metric if `auto_repeat_suppress_metric_emitted_` is false
-    // as the metric should only be emitted once per Press->Release cycle.
+  if (event.is_repeat()) {
+    // Check for cases where auto-repeat behavior is suppressed for characters
+    // with no available diacritic suggestion. Only emit the metric if
+    // `auto_repeat_suppress_metric_emitted_` is false as the metric should only
+    // be emitted once per Press->Release cycle.
     if (!auto_repeat_suppress_metric_emitted_ &&
         !longpress_diacritics_suggester_.HasDiacriticSuggestions(
-            event.GetCharacter())) {
+            event.GetCharacter()) &&
+        !IsLongpressEnabledControlV(event)) {
       auto_repeat_suppress_metric_emitted_ = true;
       RecordLongPressDiacriticAutoRepeatSuppressedMetric();
     }
@@ -402,6 +428,11 @@
   if (current_longpress_keydown_ == absl::nullopt &&
       event.type() == ui::EventType::ET_KEY_PRESSED) {
     current_longpress_keydown_ = event;
+
+    if (IsLongpressEnabledControlV(event)) {
+      longpress_control_v_suggester_.CachePastedTextStart();
+    }
+
     longpress_timer_.Start(
         FROM_HERE, kLongpressActivationDelay,
         base::BindOnce(&AssistiveSuggester::OnLongpressDetected,
@@ -426,17 +457,39 @@
       // support IME operations.
       last_surrounding_text_.empty() || last_cursor_pos_ <= 0 ||
       static_cast<int>(last_surrounding_text_.length()) < last_cursor_pos_ ||
-      last_surrounding_text_[last_cursor_pos_ - 1] !=
-          current_longpress_keydown_->GetCharacter()) {
+      (!IsLongpressEnabledControlV(current_longpress_keydown_.value()) &&
+       last_surrounding_text_[last_cursor_pos_ - 1] !=
+           current_longpress_keydown_->GetCharacter())) {
     return;
   }
-  if (longpress_diacritics_suggester_.TrySuggestOnLongpress(
-          current_longpress_keydown_->GetCharacter())) {
+
+  if (IsLongpressEnabledControlV(current_longpress_keydown_.value())) {
+    current_suggester_ = &longpress_control_v_suggester_;
+    const auto anchor_rect =
+        IMEBridge::Get()->GetInputContextHandler()->GetTextFieldBounds();
+    Shell::Get()->clipboard_history_controller()->ShowMenu(
+        anchor_rect, ui::MenuSourceType::MENU_SOURCE_KEYBOARD,
+        crosapi::mojom::ClipboardHistoryControllerShowSource::
+            kControlVLongpress,
+        base::BindOnce(&AssistiveSuggester::OnClipboardHistoryMenuClosing,
+                       weak_ptr_factory_.GetWeakPtr()));
+  } else if (longpress_diacritics_suggester_.TrySuggestOnLongpress(
+                 current_longpress_keydown_->GetCharacter())) {
     current_suggester_ = &longpress_diacritics_suggester_;
   }
   current_longpress_keydown_ = absl::nullopt;
 }
 
+void AssistiveSuggester::OnClipboardHistoryMenuClosing(bool will_paste_item) {
+  DCHECK_EQ(current_suggester_, &longpress_control_v_suggester_);
+  if (will_paste_item) {
+    // Note: The suggestion index is irrelevant for long-pressed Ctrl+V.
+    AcceptSuggestion(/*index=*/-1);
+  } else {
+    DismissSuggestion();
+  }
+}
+
 void AssistiveSuggester::OnExternalSuggestionsUpdated(
     const std::vector<AssistiveSuggestion>& suggestions) {
   if (!IsMultiWordSuggestEnabled())
@@ -623,5 +676,4 @@
   }
 }
 
-}  // namespace input_method
-}  // namespace ash
+}  // namespace ash::input_method
diff --git a/chrome/browser/ash/input_method/assistive_suggester.h b/chrome/browser/ash/input_method/assistive_suggester.h
index fbf4428..161c05c 100644
--- a/chrome/browser/ash/input_method/assistive_suggester.h
+++ b/chrome/browser/ash/input_method/assistive_suggester.h
@@ -13,6 +13,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/ash/input_method/assistive_suggester_switch.h"
 #include "chrome/browser/ash/input_method/emoji_suggester.h"
+#include "chrome/browser/ash/input_method/longpress_control_v_suggester.h"
 #include "chrome/browser/ash/input_method/longpress_diacritics_suggester.h"
 #include "chrome/browser/ash/input_method/multi_word_suggester.h"
 #include "chrome/browser/ash/input_method/suggester.h"
@@ -22,8 +23,7 @@
 #include "chromeos/ash/services/ime/public/cpp/assistive_suggestions.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-namespace ash {
-namespace input_method {
+namespace ash::input_method {
 
 // An agent to suggest assistive information when the user types, and adopt or
 // dismiss the suggestion according to the user action.
@@ -167,10 +167,15 @@
 
   void OnLongpressDetected();
 
+  // Accepts or dismisses a Ctrl+V long-press suggestion based on the exit
+  // status of the clipboard history menu, as indicated by `will_paste_item`.
+  void OnClipboardHistoryMenuClosing(bool will_paste_item);
+
   Profile* profile_;
   EmojiSuggester emoji_suggester_;
   MultiWordSuggester multi_word_suggester_;
   LongpressDiacriticsSuggester longpress_diacritics_suggester_;
+  LongpressControlVSuggester longpress_control_v_suggester_;
   std::unique_ptr<AssistiveSuggesterSwitch> suggester_switch_;
 
   // The id of the currently active input engine.
@@ -204,7 +209,6 @@
   base::WeakPtrFactory<AssistiveSuggester> weak_ptr_factory_{this};
 };
 
-}  // namespace input_method
-}  // namespace ash
+}  // namespace ash::input_method
 
 #endif  // CHROME_BROWSER_ASH_INPUT_METHOD_ASSISTIVE_SUGGESTER_H_
diff --git a/chrome/browser/ash/input_method/assistive_suggester_unittest.cc b/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
index 376ae585..697d764 100644
--- a/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
+++ b/chrome/browser/ash/input_method/assistive_suggester_unittest.cc
@@ -4,10 +4,15 @@
 
 #include "chrome/browser/ash/input_method/assistive_suggester.h"
 
+#include "ash/clipboard/clipboard_history_controller_impl.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/functional/bind.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/repeating_test_future.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/input_method/assistive_suggester_client_filter.h"
@@ -25,12 +30,16 @@
 #include "components/prefs/scoped_user_pref_update.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
 #include "ui/base/ime/ash/ime_bridge.h"
 #include "ui/events/base_event_utils.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
 #include "ui/events/keycodes/dom/dom_code.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
 
-namespace ash {
-namespace input_method {
+namespace ash::input_method {
 namespace {
 
 using ime::AssistiveSuggestion;
@@ -293,6 +302,24 @@
   EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
 }
 
+TEST_F(AssistiveSuggesterTest,
+       AssistiveControlVLongpressFlagEnabled_AssistiveFeatureEnabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kClipboardHistoryLongpress);
+  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
+                        /*diacritics_on_longpress_enabled=*/false);
+  EXPECT_TRUE(assistive_suggester_->IsAssistiveFeatureEnabled());
+}
+
+TEST_F(AssistiveSuggesterTest,
+       AssistiveControlVLongpressFlagDisabled_AssistiveFeatureDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(features::kClipboardHistoryLongpress);
+  SetInputMethodOptions(*profile_, /*predictive_writing_enabled=*/false,
+                        /*diacritics_on_longpress_enabled=*/false);
+  EXPECT_FALSE(assistive_suggester_->IsAssistiveFeatureEnabled());
+}
+
 TEST_F(AssistiveSuggesterTest, RecordPKDiacriticsPrefEnabledOnActivate) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
@@ -1298,5 +1325,150 @@
   EXPECT_TRUE(suggestion_handler_->GetShowingSuggestion());
   EXPECT_EQ(suggestion_handler_->GetSuggestionText(), u"←;↑;→");
 }
-}  // namespace input_method
-}  // namespace ash
+
+class AssistiveSuggesterControlVLongpressTest : public AshTestBase {
+ protected:
+  AssistiveSuggesterControlVLongpressTest()
+      : AshTestBase(std::unique_ptr<base::test::TaskEnvironment>(
+            std::make_unique<content::BrowserTaskEnvironment>(
+                base::test::TaskEnvironment::TimeSource::MOCK_TIME))),
+        assistive_suggester_(
+            &suggestion_handler_,
+            &profile_,
+            std::make_unique<AssistiveSuggesterClientFilter>(
+                base::BindRepeating(&GetFocusedTabUrl),
+                base::BindRepeating(&GetFocusedWindowProperties))) {
+    feature_list_.InitAndEnableFeature(features::kClipboardHistoryLongpress);
+  }
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    Shell::Get()
+        ->clipboard_history_controller()
+        ->set_confirmed_operation_callback_for_test(
+            operation_confirmed_future_.GetCallback());
+
+    // Write content to the clipboard so that the clipboard history menu can
+    // appear.
+    SetClipboardText("B");
+    SetClipboardText("A");
+
+    // Create a test window for the clipboard history controller to recognize as
+    // a paste target.
+    gfx::Rect test_window_rect(100, 100, 100, 100);
+    std::unique_ptr<aura::Window> window(CreateTestWindow(test_window_rect));
+  }
+
+  void SetClipboardText(const std::string& text) {
+    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
+        .WriteText(base::UTF8ToUTF16(text));
+
+    // Clipboard history will post a task to process clipboard data in order to
+    // debounce multiple clipboard writes occurring in sequence. Here we give
+    // clipboard history the chance to run its posted tasks before proceeding.
+    EXPECT_TRUE(operation_confirmed_future_.Take());
+  }
+
+  ui::KeyEvent CreateControlVEvent(int extra_flags = ui::EF_NONE) {
+    return ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_V,
+                        ui::EF_CONTROL_DOWN | extra_flags);
+  }
+
+  base::test::ScopedFeatureList feature_list_;
+  TestingProfile profile_;
+  FakeSuggestionHandler suggestion_handler_;
+  AssistiveSuggester assistive_suggester_;
+  base::test::RepeatingTestFuture<bool> operation_confirmed_future_;
+  base::HistogramTester histogram_tester_;
+};
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ClipboardHistoryTriggeredOnControlVLongpress) {
+  assistive_suggester_.OnFocus(5);
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  EXPECT_TRUE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+  histogram_tester_.ExpectUniqueSample(
+      "Ash.ClipboardHistory.ContextMenu.ShowMenu",
+      crosapi::mojom::ClipboardHistoryControllerShowSource::kControlVLongpress,
+      1);
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ControlVLongpressPasteSuccessRecorded) {
+  assistive_suggester_.OnFocus(5);
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  EXPECT_TRUE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+
+  // Paste an item from the clipboard history menu.
+  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_DOWN);
+  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
+  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Success", 1);
+  histogram_tester_.ExpectUniqueSample("InputMethod.Assistive.Success",
+                                       AssistiveType::kLongpressControlV, 1);
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ClipboardHistoryDismissedNoSuccessRecorded) {
+  assistive_suggester_.OnFocus(5);
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  EXPECT_TRUE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+
+  // Dismiss the clipboard history menu without pasting.
+  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
+  GetEventGenerator()->PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
+  histogram_tester_.ExpectTotalCount("InputMethod.Assistive.Success", 0);
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ClipboardHistoryNotTriggeredIfShiftDown) {
+  assistive_suggester_.OnFocus(5);
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(
+      CreateControlVEvent(/*extra_flags=*/ui::EF_SHIFT_DOWN)));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ClipboardHistoryNotTriggeredIfNoContextForControlVLongpress) {
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(base::Seconds(1));
+
+  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       ClipboardHistoryNotTriggeredIfControlVLongpressInterrupted) {
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+  task_environment()->FastForwardBy(
+      base::Milliseconds(100));  // Not long enough to trigger longpress.
+
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(
+      ui::KeyEvent(ui::ET_KEY_RELEASED, ui::VKEY_V, ui::EF_CONTROL_DOWN)));
+  EXPECT_FALSE(Shell::Get()->clipboard_history_controller()->IsMenuShowing());
+}
+
+TEST_F(AssistiveSuggesterControlVLongpressTest,
+       RepeatedControlVNotPropagatedIfControlVLongpressEnabled) {
+  assistive_suggester_.OnFocus(5);
+  EXPECT_FALSE(assistive_suggester_.OnKeyEvent(CreateControlVEvent()));
+  assistive_suggester_.OnSurroundingTextChanged(u"A", gfx::Range(1));
+
+  // Returning true tells IME to not propagate this event.
+  EXPECT_TRUE(assistive_suggester_.OnKeyEvent(
+      CreateControlVEvent(/*extra_flags=*/ui::EF_IS_REPEAT)));
+}
+}  // namespace ash::input_method
diff --git a/chrome/browser/ash/input_method/longpress_control_v_suggester.cc b/chrome/browser/ash/input_method/longpress_control_v_suggester.cc
new file mode 100644
index 0000000..5a1b5ca0
--- /dev/null
+++ b/chrome/browser/ash/input_method/longpress_control_v_suggester.cc
@@ -0,0 +1,97 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/input_method/longpress_control_v_suggester.h"
+
+#include <string>
+
+#include "chrome/browser/ash/input_method/longpress_suggester.h"
+#include "chrome/browser/ash/input_method/suggestion_enums.h"
+#include "chrome/browser/ash/input_method/suggestion_handler_interface.h"
+#include "ui/base/ime/ash/ime_bridge.h"
+#include "ui/base/ime/ash/text_input_target.h"
+#include "ui/gfx/range/range.h"
+
+namespace ash::input_method {
+
+LongpressControlVSuggester::LongpressControlVSuggester(
+    SuggestionHandlerInterface* suggestion_handler)
+    : LongpressSuggester(suggestion_handler) {}
+
+LongpressControlVSuggester::~LongpressControlVSuggester() = default;
+
+void LongpressControlVSuggester::CachePastedTextStart() {
+  pasted_text_start_.reset();
+
+  TextInputTarget* input_context = IMEBridge::Get()->GetInputContextHandler();
+  if (!input_context) {
+    return;
+  }
+
+  pasted_text_start_ =
+      input_context->GetSurroundingTextInfo().selection_range.GetMin();
+}
+
+SuggestionStatus LongpressControlVSuggester::HandleKeyEvent(
+    const ui::KeyEvent& event) {
+  // The clipboard history controller handles the mouse and key events that
+  // allow users to select an item to paste.
+  return SuggestionStatus::kNotHandled;
+}
+
+bool LongpressControlVSuggester::TrySuggestWithSurroundingText(
+    const std::u16string& text,
+    const gfx::Range selection_range) {
+  // Pastes cause the surrounding text to change. Continue "suggesting" after
+  // such changes so that `this` remains the current suggester.
+  return true;
+}
+
+bool LongpressControlVSuggester::AcceptSuggestion(size_t index) {
+  if (!focused_context_id_.has_value()) {
+    LOG(ERROR)
+        << "suggest: Accepted long-press Ctrl+V suggestion with no context id.";
+    Reset();
+    return true;
+  }
+
+  if (auto* input_context = IMEBridge::Get()->GetInputContextHandler();
+      input_context != nullptr && pasted_text_start_.has_value()) {
+    size_t pasted_text_end =
+        input_context->GetSurroundingTextInfo().selection_range.GetMin();
+    DCHECK_GE(pasted_text_end, *pasted_text_start_);
+
+    std::string error;
+    suggestion_handler_->AcceptSuggestionCandidate(
+        *focused_context_id_, /*candidate=*/u"",
+        /*delete_previous_utf16_len=*/pasted_text_end - *pasted_text_start_,
+        &error);
+    if (!error.empty()) {
+      LOG(ERROR) << "suggest: Accepted long-press Ctrl+V suggestion without "
+                    "replacing originally pasted content: "
+                 << error;
+      Reset();
+      return true;
+    }
+  }
+
+  LOG(ERROR) << "suggest: Accepted long-press Ctrl+V suggestion without "
+                "replacing originally pasted content.";
+  Reset();
+  return true;
+}
+
+void LongpressControlVSuggester::DismissSuggestion() {
+  Reset();
+}
+
+AssistiveType LongpressControlVSuggester::GetProposeActionType() {
+  return AssistiveType::kLongpressControlV;
+}
+
+void LongpressControlVSuggester::Reset() {
+  pasted_text_start_.reset();
+}
+
+}  // namespace ash::input_method
diff --git a/chrome/browser/ash/input_method/longpress_control_v_suggester.h b/chrome/browser/ash/input_method/longpress_control_v_suggester.h
new file mode 100644
index 0000000..d18f513
--- /dev/null
+++ b/chrome/browser/ash/input_method/longpress_control_v_suggester.h
@@ -0,0 +1,48 @@
+// 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_ASH_INPUT_METHOD_LONGPRESS_CONTROL_V_SUGGESTER_H_
+#define CHROME_BROWSER_ASH_INPUT_METHOD_LONGPRESS_CONTROL_V_SUGGESTER_H_
+
+#include <cstddef>
+#include <string>
+
+#include "chrome/browser/ash/input_method/longpress_suggester.h"
+#include "chrome/browser/ash/input_method/suggestion_enums.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/events/event.h"
+#include "ui/gfx/range/range.h"
+
+namespace ash::input_method {
+
+class SuggestionHandlerInterface;
+
+class LongpressControlVSuggester : public LongpressSuggester {
+ public:
+  explicit LongpressControlVSuggester(
+      SuggestionHandlerInterface* suggestion_handler);
+  ~LongpressControlVSuggester() override;
+
+  void CachePastedTextStart();
+
+  // Suggester overrides:
+  SuggestionStatus HandleKeyEvent(const ui::KeyEvent& event) override;
+  bool TrySuggestWithSurroundingText(const std::u16string& text,
+                                     gfx::Range selection_range) override;
+  bool AcceptSuggestion(size_t index) override;
+  void DismissSuggestion() override;
+  AssistiveType GetProposeActionType() override;
+
+ private:
+  // LongpressSuggester:
+  void Reset() override;
+
+  // Starting index of the text pasted when Ctrl+V was first pressed, if there
+  // is an active long press.
+  absl::optional<size_t> pasted_text_start_;
+};
+
+}  // namespace ash::input_method
+
+#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_LONGPRESS_CONTROL_V_SUGGESTER_H_
diff --git a/chrome/browser/ash/input_method/suggestion_enums.h b/chrome/browser/ash/input_method/suggestion_enums.h
index 7c02c67..f11542c 100644
--- a/chrome/browser/ash/input_method/suggestion_enums.h
+++ b/chrome/browser/ash/input_method/suggestion_enums.h
@@ -5,8 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_INPUT_METHOD_SUGGESTION_ENUMS_H_
 #define CHROME_BROWSER_ASH_INPUT_METHOD_SUGGESTION_ENUMS_H_
 
-namespace ash {
-namespace input_method {
+namespace ash::input_method {
 
 // Must match with IMEAssistiveAction in enums.xml
 enum class AssistiveType {
@@ -26,7 +25,8 @@
   kMultiWordPrediction = 13,
   kMultiWordCompletion = 14,
   kLongpressDiacritics = 15,
-  kMaxValue = kLongpressDiacritics,
+  kLongpressControlV = 16,
+  kMaxValue = kLongpressControlV,
 };
 
 enum class SuggestionStatus {
@@ -69,7 +69,6 @@
   kMaxValue = kCompletion,
 };
 
-}  // namespace input_method
-}  // namespace ash
+}  // namespace ash::input_method
 
 #endif  // CHROME_BROWSER_ASH_INPUT_METHOD_SUGGESTION_ENUMS_H_
diff --git a/chrome/browser/creator/android/BUILD.gn b/chrome/browser/creator/android/BUILD.gn
index d87e429b..4285f47 100644
--- a/chrome/browser/creator/android/BUILD.gn
+++ b/chrome/browser/creator/android/BUILD.gn
@@ -6,7 +6,6 @@
 
 android_library("java") {
   sources = [
-    "java/src/org/chromium/chrome/browser/creator/CreatorApiBridge.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorCoordinator.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorMediator.java",
     "java/src/org/chromium/chrome/browser/creator/CreatorProfileView.java",
@@ -85,11 +84,6 @@
   ]
 }
 
-generate_jni("jni_headers") {
-  sources =
-      [ "java/src/org/chromium/chrome/browser/creator/CreatorApiBridge.java" ]
-}
-
 robolectric_library("junit") {
   sources = [
     "java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java",
diff --git a/chrome/browser/creator/android/creator_api_bridge.cc b/chrome/browser/creator/android/creator_api_bridge.cc
deleted file mode 100644
index 1793738b..0000000
--- a/chrome/browser/creator/android/creator_api_bridge.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <jni.h>
-#include <string>
-
-#include "url/gurl.h"
-
-#include "base/android/callback_android.h"
-#include "base/android/jni_android.h"
-#include "base/android/jni_string.h"
-#include "base/android/scoped_java_ref.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "chrome/browser/creator/android/jni_headers/CreatorApiBridge_jni.h"
-#include "components/creator/public/creator_api.h"
-#include "url/android/gurl_android.h"
-
-using base::android::ConvertUTF8ToJavaString;
-
-namespace creator {
-
-namespace {
-
-base::android::ScopedJavaLocalRef<jobject> ToJava(JNIEnv* env,
-                                                  const Creator& creator) {
-  return Java_Creator_Constructor(
-      env, base::android::ConvertUTF16ToJavaString(env, creator.url),
-      base::android::ConvertUTF16ToJavaString(env, creator.title));
-}
-
-// TODO(crbug/1374058): Replace this with actual access to creator api stub.
-void DoGetCreator(std::string web_channel_id,
-                  base::OnceCallback<void(Creator)> callback) {
-  std::move(callback).Run(Creator{u"alexainsley.com", u"Alex Ainsley"});
-}
-
-// TODO(crbug/1374058): Replace this with actual access to creator api stub.
-void DoGetWebId(std::string url,
-                base::OnceCallback<void(std::string)> callback) {
-  std::move(callback).Run(std::string("wId/12345"));
-}
-
-}  // namespace
-
-static void JNI_CreatorApiBridge_GetCreator(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jstring>& j_web_channel_id,
-    const base::android::JavaParamRef<jobject>& j_callback) {
-  DoGetCreator(base::android::ConvertJavaStringToUTF8(env, j_web_channel_id),
-               base::BindOnce(
-                   [](const base::android::JavaRef<jobject>& j_callback,
-                      Creator creator) {
-                     JNIEnv* env = base::android::AttachCurrentThread();
-                     base::android::RunObjectCallbackAndroid(
-                         j_callback, ToJava(env, creator));
-                   },
-                   base::android::ScopedJavaGlobalRef(j_callback)));
-}
-
-static void JNI_CreatorApiBridge_GetWebId(
-    JNIEnv* env,
-    const base::android::JavaParamRef<jstring>& j_url,
-    const base::android::JavaParamRef<jobject>& j_callback) {
-  DoGetWebId(base::android::ConvertJavaStringToUTF8(env, j_url),
-             base::BindOnce(
-                 [](const base::android::JavaRef<jobject>& j_callback,
-                    std::string webid) {
-                   JNIEnv* env = base::android::AttachCurrentThread();
-                   base::android::RunObjectCallbackAndroid(
-                       j_callback, ConvertUTF8ToJavaString(env, webid));
-                 },
-                 base::android::ScopedJavaGlobalRef(j_callback)));
-}
-
-}  // namespace creator
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorApiBridge.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorApiBridge.java
deleted file mode 100644
index 3f8f2a9..0000000
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorApiBridge.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.creator;
-
-import org.chromium.base.Callback;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-
-/**
- * A Java API for connecting to creator component
- */
-@JNINamespace("creator")
-public class CreatorApiBridge {
-    /**
-     * A Java Class to store creator metadata.
-     */
-    public static class Creator {
-        public final String url;
-        public final String title;
-
-        @CalledByNative("Creator")
-        public Creator(String url, String title) {
-            this.url = url;
-            this.title = title;
-        }
-    }
-
-    public static void getCreator(String webChannelId, Callback<Creator> callback) {
-        CreatorApiBridgeJni.get().getCreator(webChannelId, callback);
-    }
-
-    public static void getWebId(String url, Callback<String> callback) {
-        CreatorApiBridgeJni.get().getWebId(url, callback);
-    }
-
-    @NativeMethods
-    interface Natives {
-        void getCreator(String webChannelId, Callback<Creator> callback);
-        void getWebId(String url, Callback<String> callback);
-    }
-}
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
index 8724c705..9c64898 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorCoordinatorTest.java
@@ -66,8 +66,6 @@
     @Mock
     private WebFeedBridge.Natives mWebFeedBridgeJniMock;
     @Mock
-    private CreatorApiBridge.Natives mCreatorBridgeJniMock;
-    @Mock
     private FeedServiceBridge.Natives mFeedServiceBridgeJniMock;
     @Mock
     private FeedReliabilityLoggingBridge.Natives mFeedReliabilityLoggingBridgeJniMock;
@@ -106,7 +104,6 @@
     @Before
     public void setUpTest() {
         MockitoAnnotations.initMocks(this);
-        mJniMocker.mock(CreatorApiBridgeJni.TEST_HOOKS, mCreatorBridgeJniMock);
         mJniMocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock);
         mJniMocker.mock(WebFeedBridge.getTestHooksForTesting(), mWebFeedBridgeJniMock);
         mJniMocker.mock(FeedReliabilityLoggingBridge.getTestHooksForTesting(),
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediator.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediator.java
index 37fd60ce..6f2fd0c 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediator.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediator.java
@@ -8,20 +8,16 @@
 
 import android.content.Context;
 
-import org.chromium.chrome.browser.creator.CreatorApiBridge.Creator;
 import org.chromium.chrome.browser.feed.FeedServiceBridge;
 import org.chromium.chrome.browser.feed.webfeed.WebFeedBridge;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import java.nio.charset.StandardCharsets;
-
 /**
  * Sets up the Mediator for Cormorant Creator surface.  It is based on the doc at
  * https://chromium.googlesource.com/chromium/src/+/HEAD/docs/ui/android/mvc_simple_list_tutorial.md
  */
 public class CreatorMediator {
     private Context mContext;
-    private Creator mCreator;
     private PropertyModel mCreatorModel;
     private final CreatorSnackbarController mCreatorSnackbarController;
     private SignInInterstitialInitiator mSignInInterstitialInitiator;
@@ -33,9 +29,6 @@
         mCreatorModel = creatorModel;
         mCreatorSnackbarController = creatorSnackbarController;
         mSignInInterstitialInitiator = signInInterstitialInitiator;
-        if (mCreatorModel.get(CreatorProperties.WEB_FEED_ID_KEY) != null) {
-            getCreator();
-        }
 
         // Set Follow OnClick Action
         mCreatorModel.set(CreatorProperties.ON_FOLLOW_CLICK_KEY, this::followClickHandler);
@@ -67,15 +60,4 @@
                             result.requestStatus, mCreatorModel.get(CreatorProperties.TITLE_KEY));
                 });
     }
-
-    private void getCreator() {
-        CreatorApiBridge.getCreator(new String(mCreatorModel.get(CreatorProperties.WEB_FEED_ID_KEY),
-                                            StandardCharsets.UTF_8),
-                this::onGetCreator);
-    }
-
-    private void onGetCreator(Creator creator) {
-        // TODO(crbug/1374058): Get Title and Url from CreatorAPI
-        mCreator = creator;
-    }
 }
diff --git a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
index a79d22b..cd8ef85 100644
--- a/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
+++ b/chrome/browser/creator/android/java/src/org/chromium/chrome/browser/creator/CreatorMediatorTest.java
@@ -60,8 +60,6 @@
     @Mock
     private WebFeedBridge.Natives mWebFeedBridgeJniMock;
     @Mock
-    private CreatorApiBridge.Natives mCreatorBridgeJniMock;
-    @Mock
     private FeedStream.Natives mFeedStreamJniMock;
     @Mock
     private FeedServiceBridge.Natives mFeedServiceBridgeJniMock;
@@ -107,7 +105,6 @@
     @Before
     public void setUpTest() {
         MockitoAnnotations.initMocks(this);
-        mJniMocker.mock(CreatorApiBridgeJni.TEST_HOOKS, mCreatorBridgeJniMock);
         mJniMocker.mock(FeedStreamJni.TEST_HOOKS, mFeedStreamJniMock);
         mJniMocker.mock(FeedServiceBridgeJni.TEST_HOOKS, mFeedServiceBridgeJniMock);
         mJniMocker.mock(WebFeedBridge.getTestHooksForTesting(), mWebFeedBridgeJniMock);
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
index 4e315a4..8a4bd53 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_apitest.cc
@@ -36,7 +36,6 @@
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
 #include "testing/gmock/include/gmock/gmock.h"
-#include "ui/gl/gl_switches.h"
 #include "url/url_constants.h"
 
 namespace extensions {
@@ -53,7 +52,6 @@
     // Specify smallish window size to make testing of tab capture less CPU
     // intensive.
     command_line->AppendSwitchASCII(::switches::kWindowSize, "300,300");
-    command_line->AppendSwitch(::switches::kUseGpuInTests);
   }
 
   void AddExtensionToCommandLineAllowlist() {
diff --git a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
index 12ac61cd..deaf146d 100644
--- a/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
+++ b/chrome/browser/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.cc
@@ -500,10 +500,6 @@
                           base::FeatureList::IsEnabled(
                               ash::features::kHandwritingLegacyRecognition)));
   features.Append(GenerateFeatureFlag(
-      "handwritinglegacyrecognitionall",
-      base::FeatureList::IsEnabled(
-          ash::features::kHandwritingLegacyRecognitionAllLang)));
-  features.Append(GenerateFeatureFlag(
       "hindiinscriptlayout",
       base::FeatureList::IsEnabled(ash::features::kHindiInscriptLayout)));
   features.Append(GenerateFeatureFlag(
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index d259e697..755f1b1e 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -2250,9 +2250,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// TODO(crbug.com/1344548): Re-enable this test
-IN_PROC_BROWSER_TEST_F(ContentScriptApiPrerenderingTest,
-                       DISABLED_Prerendering) {
+IN_PROC_BROWSER_TEST_F(ContentScriptApiPrerenderingTest, Prerendering) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("content_scripts/prerendering")) << message_;
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index c471ebe..e8f0124 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1102,6 +1102,11 @@
     "expiry_milestone": 115
   },
   {
+    "name": "clipboard-history-longpress",
+    "owners": ["ckincaid@google.com", "multipaste@google.com"],
+    "expiry_milestone": 117
+  },
+  {
     "name": "clipboard-history-reorder",
     "owners": ["ckincaid@google.com", "multipaste@google.com"],
     "expiry_milestone": 110
@@ -2165,6 +2170,11 @@
     "expiry_milestone": 120
   },
   {
+    "name": "enable-desk-button",
+    "owners": [ "yongshun", "benbecker", "andp", "catherinez" ],
+    "expiry_milestone": 125
+  },
+  {
     "name": "enable-desks-save-and-recall",
     "owners": [ "dandersson", "yongshun", "janetmac" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index c3c4298..f772aee 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1691,12 +1691,6 @@
 const char kHandwritingLegacyRecognitionDescription[] =
     "Enables new on-device recognition for handwriting legacy paths.";
 
-const char kHandwritingLegacyRecognitionAllLangName[] =
-    "Handwriting Legacy Recognition All Languages";
-const char kHandwritingLegacyRecognitionAllLangDescription[] =
-    "Enables new on-device recognition for handwriting legacy paths in all "
-    "supported languages.";
-
 const char kHandwritingLibraryDlcName[] =
     "Handwriting recognition with library from DLC";
 const char kHandwritingLibraryDlcDescription[] =
@@ -5084,6 +5078,11 @@
 const char kCaptureModeGifRecordingDescription[] =
     "Enables the ability to record the screen into animated GIFs";
 
+extern const char kDeskButtonName[] = "Desk button in shelf";
+extern const char kDeskButtonDescription[] =
+    "Show a desk button that provides quick access to the desk menu in the "
+    "shelf in clamshell mode when there is more than one desk.";
+
 extern const char kDesks16Name[] = "Enable up to 16 virtual desks";
 extern const char kDesks16Description[] =
     "When enabled, up to 16 virtual desks are allowed.";
@@ -5137,6 +5136,13 @@
     "Enables an experimental behavior change where each time a clipboard "
     "history item is pasted, that item shifts to the top of the list.";
 
+const char kClipboardHistoryLongpressName[] =
+    "Hold Ctrl+V to paste an item from clipboard history";
+const char kClipboardHistoryLongpressDescription[] =
+    "Enables an experimental behavior change where long-pressing Ctrl+V shows "
+    "the clipboard history menu. If an item is selected to paste, it replaces "
+    "the content initially pasted by Ctrl+V.";
+
 const char kComponentUpdaterTestRequestName[] =
     "Enable the component updater check 'test-request' parameter";
 const char kComponentUpdaterTestRequestDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index a3b9ffe..cd030db 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -944,9 +944,6 @@
 extern const char kHandwritingLegacyRecognitionName[];
 extern const char kHandwritingLegacyRecognitionDescription[];
 
-extern const char kHandwritingLegacyRecognitionAllLangName[];
-extern const char kHandwritingLegacyRecognitionAllLangDescription[];
-
 extern const char kHandwritingLibraryDlcName[];
 extern const char kHandwritingLibraryDlcDescription[];
 
@@ -2916,6 +2913,9 @@
 extern const char kCaptureModeGifRecordingName[];
 extern const char kCaptureModeGifRecordingDescription[];
 
+extern const char kDeskButtonName[];
+extern const char kDeskButtonDescription[];
+
 extern const char kDesks16Name[];
 extern const char kDesks16Description[];
 
@@ -2946,6 +2946,9 @@
 extern const char kClipboardHistoryReorderName[];
 extern const char kClipboardHistoryReorderDescription[];
 
+extern const char kClipboardHistoryLongpressName[];
+extern const char kClipboardHistoryLongpressDescription[];
+
 extern const char kComponentUpdaterTestRequestName[];
 extern const char kComponentUpdaterTestRequestDescription[];
 
diff --git a/chrome/browser/media/webrtc/capture_handle_browsertest.cc b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
index 9b79746a5..267e09a 100644
--- a/chrome/browser/media/webrtc/capture_handle_browsertest.cc
+++ b/chrome/browser/media/webrtc/capture_handle_browsertest.cc
@@ -30,7 +30,6 @@
 #include "content/public/test/browser_test_base.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/prerender_test_util.h"
-#include "ui/gl/gl_switches.h"
 
 using content::WebContents;
 
@@ -222,7 +221,6 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void TearDownOnMainThread() override {
diff --git a/chrome/browser/media/webrtc/conditional_focus_browsertest.cc b/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
index b9bcb49..fb7f31bc 100644
--- a/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
+++ b/chrome/browser/media/webrtc/conditional_focus_browsertest.cc
@@ -19,7 +19,6 @@
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "third_party/blink/public/common/switches.h"
-#include "ui/gl/gl_switches.h"
 
 namespace {
 
@@ -74,7 +73,6 @@
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedPageTitle);
     command_line->AppendSwitchASCII(blink::switches::kConditionalFocusWindowMs,
                                     "5000");
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   WebContents* OpenTestPageInNewTab(const std::string& test_url) {
diff --git a/chrome/browser/media/webrtc/region_capture_browsertest.cc b/chrome/browser/media/webrtc/region_capture_browsertest.cc
index c699d74..f28a3af 100644
--- a/chrome/browser/media/webrtc/region_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/region_capture_browsertest.cc
@@ -33,7 +33,6 @@
 #include "content/public/test/prerender_test_util.h"
 #include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "third_party/blink/public/common/features.h"
-#include "ui/gl/gl_switches.h"
 
 // TODO(crbug.com/1215089): Enable this test suite on Lacros.
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -294,7 +293,6 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     command_line->AppendSwitch(
         switches::kEnableExperimentalWebPlatformFeatures);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
     command_line_ = command_line;
   }
 
diff --git a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
index d430468a..78a9a6c6 100644
--- a/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_desktop_capture_browsertest.cc
@@ -39,7 +39,6 @@
 #include "content/public/test/browser_test_utils.h"
 #include "media/base/media_switches.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
-#include "ui/gl/gl_switches.h"
 
 namespace {
 static const char kMainWebrtcTestHtmlPage[] = "/webrtc/webrtc_jsep01_test.html";
@@ -232,7 +231,6 @@
     command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
                                     "Entire screen");
     command_line->AppendSwitch(switches::kEnableUserMediaScreenCapturing);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
  protected:
diff --git a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
index e520684..f3e8c04 100644
--- a/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_getdisplaymedia_browsertest.cc
@@ -44,7 +44,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/features.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/gl/gl_switches.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "base/mac/mac_util.h"
@@ -415,7 +414,6 @@
         switches::kUseFakeDeviceForMediaStream,
         base::StringPrintf("display-media-type=%s",
                            test_config_.display_surface));
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   bool PreferCurrentTab() const override {
@@ -589,7 +587,6 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kAppWindowTitle);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void SetUpOnMainThread() override {
@@ -655,7 +652,6 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kSameOriginRenamedTitle);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   void SetUpOnMainThread() override {
@@ -828,7 +824,6 @@
         switches::kUseFakeDeviceForMediaStream,
         base::StrCat({"display-media-type=",
                       DisplaySurfaceTypeAsString(display_surface_type_)}));
-    command_line->AppendSwitch(switches::kUseGpuInTests);
   }
 
   std::string GetVideoTrackType() {
@@ -1125,8 +1120,6 @@
         switches::kEnableExperimentalWebPlatformFeatures);
     command_line->AppendSwitchASCII(
         switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
-    command_line->AppendSwitch(switches::kUseGpuInTests);
-
     if (!user_shared_audio_) {
       command_line->AppendSwitch(switches::kScreenCaptureAudioDefaultUnchecked);
     }
diff --git a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
index 1ecdeb3c..6518ff3 100644
--- a/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
+++ b/chrome/browser/media/webrtc/webrtc_text_log_handler.cc
@@ -527,10 +527,7 @@
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
   if (media::IsChromeWideEchoCancellationEnabled()) {
     LogToCircularBuffer(base::StrCat(
-        {"ChromeWideEchoCancellation : Enabled", ", processing_fifo_size = ",
-         NumberToString(
-             media::kChromeWideEchoCancellationProcessingFifoSize.Get()),
-         ", minimize_resampling = ",
+        {"ChromeWideEchoCancellation : Enabled", ", minimize_resampling = ",
          media::kChromeWideEchoCancellationMinimizeResampling.Get() ? "true"
                                                                     : "false",
          ", allow_all_sample_rates = ",
@@ -540,6 +537,14 @@
   } else {
     LogToCircularBuffer("ChromeWideEchoCancellation : Disabled");
   }
+
+  if (base::FeatureList::IsEnabled(media::kDecreaseProcessingAudioFifoSize)) {
+    LogToCircularBuffer(base::StrCat(
+        {"DecreaseProcessingAudioFifoSize : Enabled", ", fifo_size = ",
+         base::NumberToString(media::GetProcessingAudioFifoSize())}));
+  } else {
+    LogToCircularBuffer("DecreaseProcessingAudioFifoSize : Disabled");
+  }
 #endif
 
   // Audio manager
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler.cc b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler.cc
index a9b72ea7..a8156a6 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler.cc
@@ -48,6 +48,20 @@
 // visit.
 constexpr int kMinRequiredVisits = 3;
 
+// This enum must match the numbering for NTPHistoryClustersIneligibleReason in
+// enums.xml. Do not reorder or remove items, and update kMaxValue when new
+// items are added.
+enum NTPHistoryClustersIneligibleReason {
+  kNone = 0,
+  kNoClusters = 1,
+  kNonProminent = 2,
+  kNoSRPVisit = 3,
+  kInsufficientVisits = 4,
+  kInsufficientImages = 5,
+  kInsufficientRelatedSearches = 6,
+  kMaxValue = kInsufficientRelatedSearches,
+};
+
 base::flat_set<std::string> GetCategories(const char* feature_param) {
   std::string categories_string = base::GetFieldTrialParamValueByFeature(
       ntp_features::kNtpHistoryClustersModuleCategories, feature_param);
@@ -211,11 +225,16 @@
     return;
   }
 
+  history_clusters::CoalesceRelatedSearches(clusters);
+
   // Cull clusters that do not have the minimum number of visits with and
   // without images to be eligible for display.
+  NTPHistoryClustersIneligibleReason ineligible_reason =
+      clusters.empty() ? kNoClusters : kNone;
   base::EraseIf(clusters, [&](auto& cluster) {
     // Cull non prominent clusters.
     if (!cluster.should_show_on_prominent_ui_surfaces) {
+      ineligible_reason = kNonProminent;
       return true;
     }
 
@@ -228,6 +247,7 @@
               visit.normalized_url, template_url_service->search_terms_data());
         });
     if (srp_visits_it == cluster.visits.end()) {
+      ineligible_reason = kNoSRPVisit;
       return true;
     }
 
@@ -247,20 +267,33 @@
     int visits_with_images = std::accumulate(
         cluster.visits.begin(), cluster.visits.end(), 0,
         [](const auto& i, const auto& v) {
-          return i +
-                 int(v.annotated_visit.content_annotations.has_url_keyed_image);
+          return i + int(v.annotated_visit.content_annotations
+                             .has_url_keyed_image &&
+                         v.annotated_visit.visit_row.is_known_to_sync);
         });
-    return cluster.visits.size() < kMinRequiredVisits ||
-           visits_with_images < GetMinImagesToShow();
+
+    if (cluster.visits.size() < kMinRequiredVisits) {
+      ineligible_reason = kInsufficientVisits;
+      return true;
+    }
+
+    if (visits_with_images < GetMinImagesToShow()) {
+      ineligible_reason = kInsufficientImages;
+      return true;
+    }
+
+    // Cull clusters that do not have the minimum required number of related
+    // searches to be eligible for display.
+    if (cluster.related_searches.size() < kMinRequiredRelatedSearches) {
+      ineligible_reason = kInsufficientRelatedSearches;
+      return true;
+    }
+
+    return false;
   });
 
-  history_clusters::CoalesceRelatedSearches(clusters);
-  // Cull clusters that do not have the minimum required number of related
-  // searches to be eligible for display.
-  base::EraseIf(clusters, [&](auto& cluster) {
-    return cluster.related_searches.size() < kMinRequiredRelatedSearches;
-  });
-
+  base::UmaHistogramEnumeration("NewTabPage.HistoryClusters.IneligibleReason",
+                                ineligible_reason);
   base::UmaHistogramBoolean("NewTabPage.HistoryClusters.HasClusterToShow",
                             !clusters.empty());
   base::UmaHistogramCounts100("NewTabPage.HistoryClusters.NumClusterCandidates",
diff --git a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler_unittest.cc b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler_unittest.cc
index e48aac6..71711ab 100644
--- a/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/history_clusters/history_clusters_page_handler_unittest.cc
@@ -166,14 +166,17 @@
   std::unique_ptr<HistoryClustersPageHandler> handler_;
 };
 
-history::ClusterVisit SampleVisitForURL(GURL url) {
+history::ClusterVisit SampleVisitForURL(
+    GURL url,
+    bool has_url_keyed_image = true,
+    const std::vector<std::string>& related_searches = {}) {
   history::VisitRow visit_row;
   visit_row.visit_id = 1;
   visit_row.visit_time = base::Time::Now();
+  visit_row.is_known_to_sync = true;
   auto content_annotations = history::VisitContentAnnotations();
-  content_annotations.has_url_keyed_image = true;
-  content_annotations.related_searches = {"fruits", "red fruits",
-                                          "healthy fruits"};
+  content_annotations.has_url_keyed_image = has_url_keyed_image;
+  content_annotations.related_searches = related_searches;
   history::AnnotatedVisit annotated_visit;
   annotated_visit.visit_row = std::move(visit_row);
   annotated_visit.content_annotations = std::move(content_annotations);
@@ -186,11 +189,14 @@
   return sample_visit;
 }
 
-history::Cluster SampleCluster(int srp_visits, int non_srp_visits) {
+history::Cluster SampleCluster(int srp_visits,
+                               int non_srp_visits,
+                               const std::vector<std::string> related_searches =
+                                   {"fruits", "red fruits", "healthy fruits"}) {
   history::ClusterVisit sample_srp_visit =
-      SampleVisitForURL(GURL(kSampleSearchUrl));
+      SampleVisitForURL(GURL(kSampleSearchUrl), false);
   history::ClusterVisit sample_non_srp_visit =
-      SampleVisitForURL(GURL(kSampleNonSearchUrl));
+      SampleVisitForURL(GURL(kSampleNonSearchUrl), true, related_searches);
 
   std::vector<history::ClusterVisit> visits;
   visits.insert(visits.end(), srp_visits, sample_srp_visit);
@@ -233,6 +239,8 @@
             cluster_mojom->visits[0]->url_for_display);
 
   histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 0, 1);
+  histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.HasClusterToShow", true, 1);
   histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.NumClusterCandidates", 1, 1);
@@ -279,6 +287,154 @@
       "NewTabPage.HistoryClusters.NumRelatedSearches", 3, 1);
 }
 
+TEST_F(HistoryClustersPageHandlerTest, IneligibleClusterNonProminent) {
+  base::HistogramTester histogram_tester;
+
+  const history::Cluster kSampleCluster = history::Cluster(
+      1, {},
+      {{u"apples", history::ClusterKeywordData()},
+       {u"Red Oranges", history::ClusterKeywordData()}},
+      /*should_show_on_prominent_ui_surfaces=*/false,
+      /*label=*/
+      l10n_util::GetStringFUTF16(
+          IDS_HISTORY_CLUSTERS_CLUSTER_LABEL_SEARCH_TERMS, u"Red fruits"));
+  test_history_clusters_service().SetClustersToReturn({kSampleCluster});
+
+  history_clusters::mojom::ClusterPtr cluster_mojom;
+  base::MockCallback<HistoryClustersPageHandler::GetClusterCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&cluster_mojom](history_clusters::mojom::ClusterPtr cluster_arg) {
+            cluster_mojom = std::move(cluster_arg);
+          }));
+  handler().GetCluster(callback.Get());
+  ASSERT_FALSE(cluster_mojom);
+
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 2, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
+}
+
+TEST_F(HistoryClustersPageHandlerTest, IneligibleClusterNoSRPVisit) {
+  base::HistogramTester histogram_tester;
+
+  const history::Cluster kSampleCluster =
+      SampleCluster(/*srp_visits=*/0, /*non_srp_visits=*/3);
+  test_history_clusters_service().SetClustersToReturn({kSampleCluster});
+
+  history_clusters::mojom::ClusterPtr cluster_mojom;
+  base::MockCallback<HistoryClustersPageHandler::GetClusterCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&cluster_mojom](history_clusters::mojom::ClusterPtr cluster_arg) {
+            cluster_mojom = std::move(cluster_arg);
+          }));
+  handler().GetCluster(callback.Get());
+  ASSERT_FALSE(cluster_mojom);
+
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 3, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
+}
+
+TEST_F(HistoryClustersPageHandlerTest, IneligibleClusterInsufficientVisits) {
+  base::HistogramTester histogram_tester;
+
+  const history::Cluster kSampleCluster =
+      SampleCluster(/*srp_visits=*/1, /*non_srp_visits=*/1);
+  test_history_clusters_service().SetClustersToReturn({kSampleCluster});
+
+  history_clusters::mojom::ClusterPtr cluster_mojom;
+  base::MockCallback<HistoryClustersPageHandler::GetClusterCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&cluster_mojom](history_clusters::mojom::ClusterPtr cluster_arg) {
+            cluster_mojom = std::move(cluster_arg);
+          }));
+  handler().GetCluster(callback.Get());
+  ASSERT_FALSE(cluster_mojom);
+
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 4, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
+}
+
+TEST_F(HistoryClustersPageHandlerTest, IneligibleClusterInsufficientImages) {
+  base::HistogramTester histogram_tester;
+
+  history::ClusterVisit sample_srp_visit =
+      SampleVisitForURL(GURL(kSampleSearchUrl), false);
+  history::ClusterVisit sample_non_srp_visit =
+      SampleVisitForURL(GURL(kSampleNonSearchUrl), false);
+
+  const history::Cluster kSampleCluster = history::Cluster(
+      1, {sample_srp_visit, sample_non_srp_visit, sample_non_srp_visit},
+      {{u"apples", history::ClusterKeywordData()},
+       {u"Red Oranges", history::ClusterKeywordData()}},
+      /*should_show_on_prominent_ui_surfaces=*/true,
+      /*label=*/
+      l10n_util::GetStringFUTF16(
+          IDS_HISTORY_CLUSTERS_CLUSTER_LABEL_SEARCH_TERMS, u"Red fruits"));
+  test_history_clusters_service().SetClustersToReturn({kSampleCluster});
+
+  history_clusters::mojom::ClusterPtr cluster_mojom;
+  base::MockCallback<HistoryClustersPageHandler::GetClusterCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&cluster_mojom](history_clusters::mojom::ClusterPtr cluster_arg) {
+            cluster_mojom = std::move(cluster_arg);
+          }));
+  handler().GetCluster(callback.Get());
+  ASSERT_FALSE(cluster_mojom);
+
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 5, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
+}
+
+TEST_F(HistoryClustersPageHandlerTest,
+       IneligibleClusterInsufficientRelatedSearches) {
+  base::HistogramTester histogram_tester;
+
+  const history::Cluster kSampleCluster = SampleCluster(
+      /*srp_visits=*/1, /*non_srp_visits=*/2, /*related_searches=*/{});
+  test_history_clusters_service().SetClustersToReturn({kSampleCluster});
+
+  history_clusters::mojom::ClusterPtr cluster_mojom;
+  base::MockCallback<HistoryClustersPageHandler::GetClusterCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_))
+      .Times(1)
+      .WillOnce(testing::Invoke(
+          [&cluster_mojom](history_clusters::mojom::ClusterPtr cluster_arg) {
+            cluster_mojom = std::move(cluster_arg);
+          }));
+  handler().GetCluster(callback.Get());
+  ASSERT_FALSE(cluster_mojom);
+
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 6, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
+  histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
+}
+
 TEST_F(HistoryClustersPageHandlerTest, GetFakeCluster) {
   const unsigned long kNumVisits = 2;
   const unsigned long kNumVisitsWithImages = 2;
@@ -334,6 +490,8 @@
             cluster_mojom->visits[0]->url_for_display);
 
   histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 0, 1);
+  histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.HasClusterToShow", true, 1);
   histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.NumClusterCandidates", 2, 1);
@@ -384,6 +542,8 @@
   ASSERT_FALSE(cluster_mojom);
 
   histogram_tester.ExpectUniqueSample(
+      "NewTabPage.HistoryClusters.IneligibleReason", 1, 1);
+  histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.HasClusterToShow", false, 1);
   histogram_tester.ExpectUniqueSample(
       "NewTabPage.HistoryClusters.NumClusterCandidates", 0, 1);
@@ -458,14 +618,18 @@
   const GURL url_C = GURL("https://www.baz.com");
   MockCartService& cart_service = mock_cart_service();
 
-  const history::Cluster cluster = history::Cluster(
-      1,
-      {SampleVisitForURL(GURL(kSampleSearchUrl)), SampleVisitForURL(url_A),
-       SampleVisitForURL(url_B), SampleVisitForURL(url_C)},
-      {{u"apples", history::ClusterKeywordData()},
-       {u"Red Oranges", history::ClusterKeywordData()}},
-      /*should_show_on_prominent_ui_surfaces=*/true,
-      /*label=*/base::UTF8ToUTF16(kSampleLabel));
+  const std::vector<std::string> visit_related_searches = {
+      "fruits", "red fruits", "healthy fruits"};
+  const history::Cluster cluster =
+      history::Cluster(1,
+                       {SampleVisitForURL(GURL(kSampleSearchUrl), false, {}),
+                        SampleVisitForURL(url_A, true, visit_related_searches),
+                        SampleVisitForURL(url_B, true, visit_related_searches),
+                        SampleVisitForURL(url_C, true, visit_related_searches)},
+                       {{u"apples", history::ClusterKeywordData()},
+                        {u"Red Oranges", history::ClusterKeywordData()}},
+                       /*should_show_on_prominent_ui_surfaces=*/true,
+                       /*label=*/base::UTF8ToUTF16(kSampleLabel));
   test_history_clusters_service().SetClustersToReturn({cluster});
 
   // Vectors to capture mocked method args.
diff --git a/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc b/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
index 0d0cd373..7ac8821 100644
--- a/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
+++ b/chrome/browser/optimization_guide/hints_fetcher_browsertest.cc
@@ -1005,11 +1005,10 @@
             run_loop.get()));
     run_loop->Run();
 
-    // Second time should not refetch since no hosts or urls match.
+    // Second time should refetch since on-demand always fetches.
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsFetcher.RequestStatus.Bookmarks",
-        optimization_guide::HintsFetcherRequestStatus::kNoHostsOrURLsToFetch,
-        1);
+        optimization_guide::HintsFetcherRequestStatus::kSuccess, 1);
   }
 }
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index 15ee9c7..f8cef891 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -196,6 +196,10 @@
   // The store of hints.
   std::unique_ptr<optimization_guide::OptimizationGuideStore> hint_store_;
 
+  // The logger that plumbs the debug logs to the optimization guide
+  // internals page. Must outlive `prediction_manager_` and `hints_manager_`.
+  std::unique_ptr<OptimizationGuideLogger> optimization_guide_logger_;
+
   // Manages the storing, loading, and fetching of hints.
   std::unique_ptr<optimization_guide::ChromeHintsManager> hints_manager_;
 
@@ -205,10 +209,6 @@
   std::unique_ptr<optimization_guide::OptimizationGuideStore>
       prediction_model_and_features_store_;
 
-  // The logger that plumbs the debug logs to the optimization guide
-  // internals page. Must outlive `prediction_manager_`.
-  std::unique_ptr<OptimizationGuideLogger> optimization_guide_logger_;
-
   // Manages the storing, loading, and evaluating of optimization target
   // prediction models.
   std::unique_ptr<optimization_guide::PredictionManager> prediction_manager_;
diff --git a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
index 15c3a09dd..9219afa 100644
--- a/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
+++ b/chrome/browser/page_load_metrics/page_load_metrics_browsertest.cc
@@ -3695,6 +3695,37 @@
   observer.WaitForNavigationFinished();
 }
 
+IN_PROC_BROWSER_TEST_F(PageLoadMetricsBrowserTestWithFencedFrames,
+                       PageLoadPrivacySandboxAdsFencedFramesMetrics) {
+  ASSERT_TRUE(https_server().Start());
+
+  static constexpr char
+      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint[] =
+          "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
+          "NavigationToFirstContentfulPaint.FencedFrames";
+
+  // Not recorded as fenced frame is not created.
+  auto waiter1 = CreatePageLoadMetricsTestWaiter("waiter1");
+  waiter1->AddPageExpectation(TimingField::kFirstContentfulPaint);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(), https_server().GetURL("a.test", "/title1.html")));
+  waiter1->Wait();
+
+  histogram_tester_->ExpectTotalCount(
+      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 0);
+
+  // Recorded as fenced frame is created.
+  auto waiter2 = CreatePageLoadMetricsTestWaiter("waiter2");
+  waiter2->AddPageExpectation(TimingField::kFirstContentfulPaint);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(
+      browser(),
+      https_server().GetURL("c.test", "/fenced_frames/basic_title.html")));
+  waiter2->Wait();
+
+  histogram_tester_->ExpectTotalCount(
+      kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaint, 1);
+}
+
 class PageLoadMetricsBrowserTestWithBackForwardCache
     : public PageLoadMetricsBrowserTest {
  public:
diff --git a/chrome/browser/policy/policy_test_utils.h b/chrome/browser/policy/policy_test_utils.h
index 51b696e..6f764466 100644
--- a/chrome/browser/policy/policy_test_utils.h
+++ b/chrome/browser/policy/policy_test_utils.h
@@ -7,7 +7,6 @@
 
 #include "base/callback_list.h"
 #include "base/files/file_path.h"
-#include "build/chromeos_buildflags.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "components/policy/core/common/mock_configuration_policy_provider.h"
 #include "components/security_interstitials/core/controller_client.h"
@@ -40,18 +39,12 @@
 
   void SetUpOnMainThread() override;
 
-  void SetScreenshotPolicy(bool enabled);
-
   void UpdateProviderPolicy(const PolicyMap& policy);
 
   static void SetPolicy(PolicyMap* policies,
                         const char* key,
                         absl::optional<base::Value> value);
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  void TestScreenshotFile(bool enabled);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
-
   static bool FetchSubresource(content::WebContents* web_contents,
                                const GURL& url);
 
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
index 15b913db..d10f661 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/search_prefetch_service_browsertest.cc
@@ -2023,7 +2023,7 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
 
@@ -2069,7 +2069,7 @@
     EXPECT_EQ(SearchPrefetchStatus::kInFlight, prefetch_status.value());
   }
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   if (BlockOnHeadersEnabled()) {
     WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
@@ -2117,7 +2117,7 @@
   GURL canonical_search_url = GetCanonicalSearchURL(
       autocomplete_controller->result().match_at(0).destination_url);
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
   DispatchDelayedResponseTask();
 
@@ -2167,7 +2167,7 @@
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kCanBeServed);
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   // Wait until it is served to a real navigation.
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
@@ -2234,7 +2234,7 @@
       SecurityStateTabHelper::FromWebContents(GetWebContents());
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kCanBeServed);
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   // Wait until it is served to a real navigation.
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
@@ -2284,7 +2284,7 @@
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kCanBeServed);
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
   // Wait until it is served to the navigation.
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
 
@@ -2338,7 +2338,7 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kCanBeServed, prefetch_status.value());
   omnibox->model()->OnUpOrDownKeyPressed(1);
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kRequestCancelled);
@@ -3179,7 +3179,7 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kInFlight, prefetch_status.value());
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kRequestCancelled);
@@ -3243,7 +3243,7 @@
   WaitUntilStatusChangesTo(canonical_search_url,
                            SearchPrefetchStatus::kCanBeServed);
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   WaitUntilStatusChangesTo(canonical_search_url, absl::nullopt);
 
@@ -3841,7 +3841,7 @@
   ASSERT_TRUE(prefetch_status.has_value());
   EXPECT_EQ(SearchPrefetchStatus::kComplete, prefetch_status.value());
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
       canonical_search_url);
@@ -3886,7 +3886,7 @@
           canonical_search_url);
   EXPECT_FALSE(prefetch_status.has_value());
 
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
 
   prefetch_status = search_prefetch_service->GetSearchPrefetchStatusForTesting(
       canonical_search_url);
diff --git a/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc b/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
index 4c0e2f5d..03b92c5a 100644
--- a/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
+++ b/chrome/browser/preloading/prefetch/search_prefetch/search_preload_unified_browsertest.cc
@@ -1422,7 +1422,7 @@
   // 4. Click and activate.
   content::test::PrerenderHostObserver prerender_observer(
       *GetActiveWebContents(), expected_prerender_url);
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
   prerender_observer.WaitForActivation();
   histogram_tester.ExpectUniqueSample(
       "Omnibox.SearchPrefetch.PrefetchFinalStatus.SuggestionPrefetch",
@@ -1500,7 +1500,7 @@
   // 5. Click the result.
   content::TestNavigationObserver navigation_observer(GetActiveWebContents(),
                                                       1);
-  omnibox->model()->AcceptInput(WindowOpenDisposition::CURRENT_TAB);
+  omnibox->model()->OpenSelection();
   navigation_observer.Wait();
 
   // 6. Fire the timer to make all prefetch requests expire
diff --git a/chrome/browser/preloading/prerender/prerender_omnibox_ui_browsertest.cc b/chrome/browser/preloading/prerender/prerender_omnibox_ui_browsertest.cc
index aa90fcb..75716d6 100644
--- a/chrome/browser/preloading/prerender/prerender_omnibox_ui_browsertest.cc
+++ b/chrome/browser/preloading/prerender/prerender_omnibox_ui_browsertest.cc
@@ -173,13 +173,11 @@
   }
 
   void SelectAutocompleteMatchAndWaitForActivation(
-      const AutocompleteMatch& match,
+      OmniboxPopupSelection selection,
       int host_id) {
-    GURL url = match.destination_url;
     content::test::PrerenderHostObserver prerender_observer(
         *GetActiveWebContents(), host_id);
-    omnibox()->model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB,
-                                  url, std::u16string(), 0);
+    omnibox()->model()->OpenSelection(selection);
     prerender_observer.WaitForActivation();
   }
 
@@ -977,7 +975,10 @@
 
   content::NavigationHandleObserver activation_observer(
       GetActiveWebContents(), prerender_match->destination_url);
-  SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id);
+  SelectAutocompleteMatchAndWaitForActivation(
+      OmniboxPopupSelection(std::distance(
+          autocomplete_controller->result().begin(), prerender_match)),
+      host_id);
   EXPECT_TRUE(IsPrerenderingNavigation());
 
   // Wait until the history is updated.
@@ -1131,7 +1132,10 @@
   ASSERT_NE(prerender_match, std::end(autocomplete_controller->result()));
   content::NavigationHandleObserver activation_observer(
       GetActiveWebContents(), prerender_match->destination_url);
-  SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id);
+  SelectAutocompleteMatchAndWaitForActivation(
+      OmniboxPopupSelection(std::distance(
+          autocomplete_controller->result().begin(), prerender_match)),
+      host_id);
   EXPECT_TRUE(IsPrerenderingNavigation());
   base::RunLoop().RunUntilIdle();
 
@@ -1221,7 +1225,10 @@
 
   content::NavigationHandleObserver activation_observer(
       GetActiveWebContents(), prerender_match->destination_url);
-  SelectAutocompleteMatchAndWaitForActivation(*prerender_match, host_id2);
+  SelectAutocompleteMatchAndWaitForActivation(
+      OmniboxPopupSelection(std::distance(
+          autocomplete_controller->result().begin(), prerender_match)),
+      host_id2);
   EXPECT_TRUE(IsPrerenderingNavigation());
 
   {
diff --git a/chrome/browser/printing/system_access_process_print_browsertest.cc b/chrome/browser/printing/system_access_process_print_browsertest.cc
index 960fc92..d4e9d922 100644
--- a/chrome/browser/printing/system_access_process_print_browsertest.cc
+++ b/chrome/browser/printing/system_access_process_print_browsertest.cc
@@ -711,6 +711,19 @@
   int print_job_destruction_count_ = 0;
 };
 
+#if BUILDFLAG(ENABLE_OOP_PRINTING)
+
+// Values for parameterized testing.
+enum class PrintBackendFeatureVariation {
+  // `PrintBackend` calls occur from browser process.
+  kInBrowserProcess,
+  // Use OOP `PrintBackend`.  Attempt to have `PrintBackendService` be
+  // sandboxed.
+  kOopSandboxedService,
+  // Use OOP `PrintBackend`.  Always use `PrintBackendService` unsandboxed.
+  kOopUnsandboxedService,
+};
+
 class SystemAccessProcessSandboxedServicePrintBrowserTest
     : public SystemAccessProcessPrintBrowserTestBase {
  public:
@@ -721,22 +734,24 @@
   bool SandboxService() override { return true; }
 };
 
-#if BUILDFLAG(ENABLE_OOP_PRINTING)
-
 class SystemAccessProcessServicePrintBrowserTest
     : public SystemAccessProcessPrintBrowserTestBase,
-      public testing::WithParamInterface<bool> {
+      public testing::WithParamInterface<PrintBackendFeatureVariation> {
  public:
   SystemAccessProcessServicePrintBrowserTest() = default;
   ~SystemAccessProcessServicePrintBrowserTest() override = default;
 
   bool UseService() override { return true; }
-  bool SandboxService() override { return GetParam(); }
+  bool SandboxService() override {
+    return GetParam() == PrintBackendFeatureVariation::kOopSandboxedService;
+  }
 };
 
-INSTANTIATE_TEST_SUITE_P(All,
-                         SystemAccessProcessServicePrintBrowserTest,
-                         testing::Bool());
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SystemAccessProcessServicePrintBrowserTest,
+    testing::Values(PrintBackendFeatureVariation::kOopSandboxedService,
+                    PrintBackendFeatureVariation::kOopUnsandboxedService));
 
 #endif
 
@@ -750,16 +765,6 @@
   bool SandboxService() override { return false; }
 };
 
-enum class PrintBackendFeatureVariation {
-  // `PrintBackend` calls occur from browser process.
-  kInBrowserProcess,
-  // Use OOP `PrintBackend`.  Attempt to have `PrintBackendService` be
-  // sandboxed.
-  kOopSandboxedService,
-  // Use OOP `PrintBackend`.  Always use `PrintBackendService` unsandboxed.
-  kOopUnsandboxedService,
-};
-
 class SystemAccessProcessPrintBrowserTest
     : public SystemAccessProcessPrintBrowserTestBase,
       public testing::WithParamInterface<PrintBackendFeatureVariation> {
diff --git a/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.html b/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.html
index 703e1c80..ef7a786 100644
--- a/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.html
+++ b/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.html
@@ -25,9 +25,13 @@
 </style>
 
 <div class="after-screen-content">
-  <h2 class="subtitle"></h2>
+  <h2 class="subtitle">
+    [[i18n('localWebApprovalsAfterSubtitle', childName)]]
+  </h2>
   <div class="details">
-    <img class="favicon" alt=""></img>
-    <div class="details-text"></div>
+    <img class="favicon" src="[[favicon]]" alt=""></img>
+    <div class="details-text">
+      [[i18n('localWebApprovalsAfterDetails', url)]]
+    </div>
   </div>
 </div>
diff --git a/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.js b/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.js
index 24573ab..ac62af04 100644
--- a/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.js
+++ b/chrome/browser/resources/chromeos/parent_access/flows/local_web_approvals_after.js
@@ -28,6 +28,34 @@
     return html`{__html_template__}`;
   }
 
+  static get properties() {
+    return {
+      childName: {type: String},
+      url: {type: String},
+      favicon: {type: String},
+    };
+  }
+
+  constructor() {
+    super();
+
+    /**
+     * Display name for the supervised user requesting website access.
+     * @protected {string}
+     */
+    this.childName = '';
+    /**
+     * URL that access is being requested for.
+     * @protected {string}
+     */
+    this.url = '';
+    /**
+     * Favicon for the website, encoded as a Base64 encoded string.
+     * @protected {string}
+     */
+    this.favicon = '';
+  }
+
   /** @override */
   ready() {
     super.ready();
@@ -51,17 +79,11 @@
    * @private
    */
   renderDetails_(params) {
-    const childName = this.decodeMojoString16_(params.childDisplayName);
-    const url = params.url.url;
+    this.childName = this.decodeMojoString16_(params.childDisplayName);
+    this.url = params.url.url;
     // Convert the PNG bytes to a Base64 encoded string.
     const favicon = btoa(String.fromCharCode(...params.faviconPngBytes));
-
-    this.shadowRoot.querySelector('.subtitle').innerText =
-        this.i18n('localWebApprovalsAfterSubtitle', childName);
-    this.shadowRoot.querySelector('.details-text').innerText =
-        this.i18n('localWebApprovalsAfterDetails', url);
-    this.shadowRoot.querySelector('.favicon').src =
-        'data:image/png;base64,' + favicon;
+    this.favicon = 'data:image/png;base64,' + favicon;
   }
 
   /**
diff --git a/chrome/browser/resources/settings/chromeos/device_page/fake_input_device_data.ts b/chrome/browser/resources/settings/chromeos/device_page/fake_input_device_data.ts
index 26e3f78..babe627c 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/fake_input_device_data.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/fake_input_device_data.ts
@@ -2,15 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {TimeDelta} from 'chrome://resources/mojo/mojo/public/mojom/base/time.mojom-webui.js';
-
 import {Keyboard, MetaKey, ModifierKey, Mouse, PointingStick, Touchpad} from './input_device_settings_types.js';
 
-
-export function mojoTimeDelta(timeDelta: number): TimeDelta {
-  return {microseconds: BigInt(Math.floor(timeDelta * 1000))};
-}
-
 export const fakeKeyboards: Keyboard[] = [
   {
     id: 0,
@@ -33,9 +26,6 @@
       },
       topRowAreFkeys: false,
       suppressMetaFkeyRewrites: false,
-      autoRepeatEnabled: false,
-      autoRepeatDelay: mojoTimeDelta(2000),
-      autoRepeatInterval: mojoTimeDelta(2000),
     },
   },
   {
@@ -56,9 +46,6 @@
       modifierRemappings: {},
       topRowAreFkeys: true,
       suppressMetaFkeyRewrites: true,
-      autoRepeatEnabled: true,
-      autoRepeatDelay: mojoTimeDelta(150),
-      autoRepeatInterval: mojoTimeDelta(20),
     },
   },
   {
@@ -79,9 +66,6 @@
       modifierRemappings: {[ModifierKey.kAlt]: ModifierKey.kAssistant},
       topRowAreFkeys: true,
       suppressMetaFkeyRewrites: false,
-      autoRepeatEnabled: true,
-      autoRepeatDelay: mojoTimeDelta(500),
-      autoRepeatInterval: mojoTimeDelta(100),
     },
   },
 ];
@@ -108,9 +92,6 @@
       },
       topRowAreFkeys: false,
       suppressMetaFkeyRewrites: false,
-      autoRepeatEnabled: false,
-      autoRepeatDelay: mojoTimeDelta(2000),
-      autoRepeatInterval: mojoTimeDelta(2000),
     },
   },
   {
@@ -131,9 +112,6 @@
       modifierRemappings: {},
       topRowAreFkeys: true,
       suppressMetaFkeyRewrites: true,
-      autoRepeatEnabled: true,
-      autoRepeatDelay: mojoTimeDelta(150),
-      autoRepeatInterval: mojoTimeDelta(20),
     },
   },
 ];
@@ -269,4 +247,4 @@
       accelerationEnabled: true,
     },
   },
-];
\ No newline at end of file
+];
diff --git a/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.html b/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.html
index fe920326..7acc52a 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.html
+++ b/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.html
@@ -41,43 +41,6 @@
         deep-link-focus-id$="[[Setting.kKeyboardFunctionKeys]]">
     </settings-toggle-button>
   </template>
-  <settings-toggle-button
-      id="enableAutoRepeatButton"
-      class="hr"
-      pref="{{enableAutoRepeatPref}}"
-      label="$i18n{keyboardEnableAutoRepeat}"
-      deep-link-focus-id$="[[Setting.kKeyboardAutoRepeat]]">
-  </settings-toggle-button>
-  <iron-collapse
-      opened="[[enableAutoRepeatPref.value]]">
-    <div class="settings-box continuation embedded">
-      <div class="start" id="repeatDelayLabel" aria-hidden="true">
-        $i18n{keyRepeatDelay}
-      </div>
-      <settings-slider id="delaySlider"
-          pref="{{autoRepeatDelaysPref}}"
-          ticks="[[autoRepeatDelays]]"
-          disabled="[[!enableAutoRepeatPref.value]]"
-          label-aria="$i18n{keyRepeatDelay}"
-          label-min="$i18n{keyRepeatDelayLong}"
-          label-max="$i18n{keyRepeatDelayShort}">
-      </settings-slider>
-    </div>
-    <div class="settings-box continuation embedded">
-      <div class="start" id="repeatRateLabel" aria-hidden="true">
-        $i18n{keyRepeatRate}
-      </div>
-      <settings-slider id="repeatRateSlider"
-          pref="{{autoRepeatIntervalsPref}}"
-          ticks="[[autoRepeatIntervals]]"
-          disabled="[[
-              !enableAutoRepeatPref.value]]"
-          label-aria="$i18n{keyRepeatRate}"
-          label-min="$i18n{keyRepeatRateSlow}"
-          label-max="$i18n{keyRepeatRateFast}">
-      </settings-slider>
-    </div>
-  </iron-collapse>
   <cr-link-row id="remapKeyboardKeys"
     class="hr" on-click="onRemapKeyboardKeysTap"
     label="$i18n{remapKeyboardKeysRowLabel}"
diff --git a/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.ts b/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.ts
index c16ae90..41ef58f 100644
--- a/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.ts
+++ b/chrome/browser/resources/settings/chromeos/device_page/per_device_keyboard_subsection.ts
@@ -33,7 +33,6 @@
 import {RouteOriginMixin} from '../route_origin_mixin.js';
 import {Route, Router} from '../router.js';
 
-import {mojoTimeDelta} from './fake_input_device_data.js';
 import {getInputDeviceSettingsProvider} from './input_device_mojo_interface_provider.js';
 import {InputDeviceSettingsProviderInterface, Keyboard, KeyboardSettings} from './input_device_settings_types.js';
 import {settingsAreEqual} from './input_device_settings_utils.js';
@@ -76,61 +75,6 @@
         },
       },
 
-      enableAutoRepeatPref: {
-        type: Object,
-        value() {
-          return {
-            key: 'fakeEnableAutoRepeatPref',
-            type: chrome.settingsPrivate.PrefType.BOOLEAN,
-            value: true,
-          };
-        },
-      },
-
-      autoRepeatDelaysPref: {
-        type: Object,
-        value() {
-          return {
-            key: 'fakeAutoRepeatDelaysPref',
-            type: chrome.settingsPrivate.PrefType.NUMBER,
-            value: 500,
-          };
-        },
-      },
-
-      autoRepeatIntervalsPref: {
-        type: Object,
-        value() {
-          return {
-            key: 'fakeAutoRepeatIntervalsPref',
-            type: chrome.settingsPrivate.PrefType.NUMBER,
-            value: 50,
-          };
-        },
-      },
-
-      /**
-       * Auto-repeat delays (in ms) for the corresponding slider values, from
-       * long to short. The values were chosen to provide a large range while
-       * giving several options near the defaults.
-       */
-      autoRepeatDelays: {
-        type: Array,
-        value: [2000, 1500, 1000, 500, 300, 200, 150],
-        readOnly: true,
-      },
-
-      /**
-       * Auto-repeat intervals (in ms) for the corresponding slider values, from
-       * long to short. The slider itself is labeled "rate", the inverse of
-       * interval, and goes from slow (long interval) to fast (short interval).
-       */
-      autoRepeatIntervals: {
-        type: Array,
-        value: [2000, 1000, 500, 300, 200, 100, 50, 30, 20],
-        readOnly: true,
-      },
-
       keyboard: {
         type: Object,
       },
@@ -182,14 +126,9 @@
   }
 
   protected keyboard: Keyboard;
-  private autoRepeatDelays: number[];
-  private autoRepeatIntervals: number[];
   private route_: Route = routes.PER_DEVICE_KEYBOARD;
   private topRowAreFunctionKeysPref: chrome.settingsPrivate.PrefObject;
   private blockMetaFunctionKeyRewritesPref: chrome.settingsPrivate.PrefObject;
-  private enableAutoRepeatPref: chrome.settingsPrivate.PrefObject;
-  private autoRepeatDelaysPref: chrome.settingsPrivate.PrefObject;
-  private autoRepeatIntervalsPref: chrome.settingsPrivate.PrefObject;
   private remapKeyboardKeysSublabel: string;
   private isInitialized: boolean = false;
   private inputDeviceSettingsProvider: InputDeviceSettingsProviderInterface =
@@ -207,14 +146,6 @@
     this.set(
         'blockMetaFunctionKeyRewritesPref.value',
         this.keyboard.settings.suppressMetaFkeyRewrites);
-    this.set(
-        'enableAutoRepeatPref.value', this.keyboard.settings.autoRepeatEnabled);
-    this.set(
-        'autoRepeatDelaysPref.value',
-        Number(this.keyboard.settings.autoRepeatDelay.microseconds) / 1000);
-    this.set(
-        'autoRepeatIntervalsPref.value',
-        Number(this.keyboard.settings.autoRepeatInterval.microseconds) / 1000);
     this.isInitialized = true;
   }
 
@@ -237,10 +168,7 @@
 
     const newSettings: KeyboardSettings = {
       ...this.keyboard.settings,
-      autoRepeatEnabled: this.enableAutoRepeatPref.value,
       topRowAreFkeys: this.topRowAreFunctionKeysPref.value,
-      autoRepeatDelay: mojoTimeDelta(this.autoRepeatDelaysPref.value),
-      autoRepeatInterval: mojoTimeDelta(this.autoRepeatIntervalsPref.value),
       suppressMetaFkeyRewrites: this.blockMetaFunctionKeyRewritesPref.value,
     };
 
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.ts b/chrome/browser/resources/settings/chromeos/lazy_load.ts
index 582a2c7b..f3afdc9b 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.ts
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.ts
@@ -121,6 +121,7 @@
 export {PrinterType} from './os_printing_page/cups_printer_types.js';
 export {CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl, PrinterSetupResult, PrintServerResult} from './os_printing_page/cups_printers_browser_proxy.js';
 export {CupsPrintersEntryManager} from './os_printing_page/cups_printers_entry_manager.js';
+export {OsSettingsPrintingPageElement} from './os_printing_page/os_printing_page.js';
 export {MediaDevicesProxy} from './os_privacy_page/media_devices_proxy.js';
 export {PrivacyHubBrowserProxy, PrivacyHubBrowserProxyImpl} from './os_privacy_page/privacy_hub_browser_proxy.js';
 export {OsResetBrowserProxyImpl} from './os_reset_page/os_reset_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
index 89edc658..40f364c 100644
--- a/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_a11y_page/chromevox_subpage.html
@@ -159,6 +159,20 @@
   </cr-link-row>
 </div>
 <h2>$i18n{chromeVoxBrailleLabel}</h2>
+<div class="sub-item">
+  <settings-toggle-button
+      id="brailleWordWrapToggle"
+      class="settings-box continuation"
+      pref="{{prefs.settings.a11y.chromevox.braille_word_wrap}}"
+      label="$i18n{chromeVoxBrailleWordWrap}">
+  </settings-toggle-button>
+  <settings-toggle-button
+      id="menuBrailleCommandsToggle"
+      class="settings-box continuation"
+      pref="{{prefs.settings.a11y.chromevox.menu_braille_commands}}"
+      label="$i18n{chromeVoxMenuBrailleCommands}">
+  </settings-toggle-button>
+</div>
 <cr-expand-button id="developerOptionsExpandButton" class="hr"
     expanded="{{developerOptionsExpanded_}}">
   <h2 id="developerOptionsHeading">$i18n{chromeVoxDeveloperOptionsLabel}</h2>
diff --git a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
index da2c47c1..5d0170a 100644
--- a/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
+++ b/chrome/browser/resources/settings/chromeos/os_printing_page/os_printing_page.ts
@@ -23,7 +23,8 @@
 const OsSettingsPrintingPageElementBase =
     DeepLinkingMixin(RouteObserverMixin(PolymerElement));
 
-class OsSettingsPrintingPageElement extends OsSettingsPrintingPageElementBase {
+export class OsSettingsPrintingPageElement extends
+    OsSettingsPrintingPageElementBase {
   static get is(): string {
     return 'os-settings-printing-page';
   }
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index e2bb599..a28728d 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -2,8 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("../settings.gni")
-
 ts_definition_files = [
   "//tools/typescript/definitions/bluetooth.d.ts",
   "//tools/typescript/definitions/bluetooth_private.d.ts",
diff --git a/chrome/browser/resources/side_panel/bookmarks/BUILD.gn b/chrome/browser/resources/side_panel/bookmarks/BUILD.gn
index 58a2e39c..aaf47a1 100644
--- a/chrome/browser/resources/side_panel/bookmarks/BUILD.gn
+++ b/chrome/browser/resources/side_panel/bookmarks/BUILD.gn
@@ -69,4 +69,6 @@
         rebase_path(
             "$root_gen_dir/chrome/browser/resources/side_panel/shared/tsc/*",
             target_gen_dir) ]
+
+  enable_source_maps = enable_webui_inline_sourcemaps
 }
diff --git a/chrome/browser/resources/side_panel/user_notes/BUILD.gn b/chrome/browser/resources/side_panel/user_notes/BUILD.gn
index cdbf4f8..1035169 100644
--- a/chrome/browser/resources/side_panel/user_notes/BUILD.gn
+++ b/chrome/browser/resources/side_panel/user_notes/BUILD.gn
@@ -39,4 +39,6 @@
     "//ui/webui/resources/js:build_ts",
     "//ui/webui/resources/mojo:build_ts",
   ]
+
+  enable_source_maps = enable_webui_inline_sourcemaps
 }
diff --git a/chrome/browser/share/android/BUILD.gn b/chrome/browser/share/android/BUILD.gn
index f384a31a..1af29f81 100644
--- a/chrome/browser/share/android/BUILD.gn
+++ b/chrome/browser/share/android/BUILD.gn
@@ -10,7 +10,6 @@
     "java/res/drawable/camera_img.xml",
     "java/res/drawable/delete_icon.xml",
     "java/res/drawable/edit_icon.xml",
-    "java/res/drawable/generic_favicon.xml",
     "java/res/drawable/generic_file.xml",
     "java/res/drawable/ink_highlighter.xml",
     "java/res/drawable/link.xml",
diff --git a/chrome/browser/share/android/java/res/drawable/generic_favicon.xml b/chrome/browser/share/android/java/res/drawable/generic_favicon.xml
deleted file mode 100644
index 68c44be2..0000000
--- a/chrome/browser/share/android/java/res/drawable/generic_favicon.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <group>
-    <clip-path
-        android:pathData="M12,2C17.52,2 22,6.48 22,12C22,17.52 17.52,22 12,22C6.48,22 2,17.52 2,12C2,6.48 6.48,2 12,2ZM12,4C7.5846,4 4,7.5846 4,12L4,12L8.3997,12C11.807,12.0217 13.3215,13.7307 12.9433,17.1271L12.9433,17.1271L9.4878,17.1271L9.4878,19.597C10.278,19.8585 11.1226,20 12,20C16.4154,20 20,16.4154 20,12C20,11.837 19.9951,11.6751 19.9855,11.5144C19.3285,12.5048 18.3333,13 17,13C14.8626,13 13.7939,12.0836 13.7939,10.2507L13.7939,10.2507L10.0457,10.2507C9.7719,7.5224 10.7285,6.1583 12.9156,6.1583C12.9156,5.1831 13.243,4.5615 13.7273,4.1873C13.1709,4.0646 12.593,4 12,4Z"/>
-    <path
-        android:pathData="M0,0h24v24h-24z"
-        android:strokeWidth="1"
-        android:fillColor="@color/modern_grey_700"
-        android:fillType="evenOdd"/>
-  </group>
-</vector>
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
index d60a001..8392cb47 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetBottomSheetContent.java
@@ -39,6 +39,7 @@
 import org.chromium.chrome.browser.share.link_to_text.LinkToTextCoordinator.LinkGeneration;
 import org.chromium.chrome.browser.share.share_sheet.ShareSheetLinkToggleCoordinator.LinkToggleState;
 import org.chromium.chrome.browser.share.share_sheet.ShareSheetLinkToggleMetricsHelper.LinkToggleMetricsDetails;
+import org.chromium.chrome.browser.ui.favicon.FaviconUtils;
 import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
 import org.chromium.chrome.browser.user_education.UserEducationHelper;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
@@ -519,20 +520,20 @@
      */
     private void onFaviconAvailable(@Nullable Bitmap icon, @ColorInt int fallbackColor,
             boolean isColorDefault, @IconType int iconType) {
+        int size = mActivity.getResources().getDimensionPixelSize(
+                R.dimen.sharing_hub_preview_inner_icon_size);
         // If we didn't get a favicon, use the generic favicon instead.
+        Bitmap scaledIcon;
         if (icon == null) {
-            setDefaultIconForPreview(
-                    AppCompatResources.getDrawable(mActivity, R.drawable.generic_favicon));
+            scaledIcon = FaviconUtils.createGenericFaviconBitmap(mActivity, size);
             RecordUserAction.record("SharingHubAndroid.GenericFaviconShown");
         } else {
-            int size = mActivity.getResources().getDimensionPixelSize(
-                    R.dimen.sharing_hub_preview_inner_icon_size);
-            Bitmap scaledIcon = Bitmap.createScaledBitmap(icon, size, size, true);
-            ImageView imageView = this.getContentView().findViewById(R.id.image_preview);
-            imageView.setImageBitmap(scaledIcon);
-            centerIcon(imageView);
+            scaledIcon = Bitmap.createScaledBitmap(icon, size, size, true);
             RecordUserAction.record("SharingHubAndroid.LinkFaviconShown");
         }
+        ImageView imageView = this.getContentView().findViewById(R.id.image_preview);
+        imageView.setImageBitmap(scaledIcon);
+        centerIcon(imageView);
     }
 
     private String getFileType(String mimeType) {
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 5a55dd2..74a5b93 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2020,6 +2020,8 @@
       "../ash/app_list/arc/arc_app_list_prefs.h",
       "../ash/app_list/arc/arc_app_list_prefs_factory.cc",
       "../ash/app_list/arc/arc_app_list_prefs_factory.h",
+      "../ash/app_list/arc/arc_app_metrics_util.cc",
+      "../ash/app_list/arc/arc_app_metrics_util.h",
       "../ash/app_list/arc/arc_app_scoped_pref_update.cc",
       "../ash/app_list/arc/arc_app_scoped_pref_update.h",
       "../ash/app_list/arc/arc_app_sync_metrics_helper.cc",
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
index 583497e..3db704e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
@@ -172,7 +172,7 @@
                     + "a transition");
         }
 
-        boolean canAnimate = mIsAnimationAllowedPredicate.getAsBoolean();
+        boolean isAnimationAllowedByParent = mIsAnimationAllowedPredicate.getAsBoolean();
 
         if (isRunningTransition()) {
             // If we are running any transitions then finish them immediately and jump to the next
@@ -195,11 +195,15 @@
         if (buttonData == null || !buttonData.canShow()) {
             mCurrentButtonVariant = AdaptiveToolbarButtonVariant.NONE;
             mCanCurrentButtonShow = false;
-            hide(canAnimate);
+            hide(isAnimationAllowedByParent);
             return;
         }
 
         ButtonSpec buttonSpec = buttonData.getButtonSpec();
+        boolean isButtonVariantChanging = mCurrentButtonVariant != buttonSpec.getButtonVariant();
+        // This boolean is final because it's passed to an inner class (OnGlobalLayoutListener).
+        final boolean canAnimate = isAnimationAllowedByParent && isButtonVariantChanging;
+
         mCurrentButtonVariant = buttonSpec.getButtonVariant();
         mCanCurrentButtonShow = buttonData.canShow();
 
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
index 1f28074..7f8d5a22 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonViewTest.java
@@ -7,6 +7,7 @@
 import static junit.framework.Assert.assertFalse;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -42,6 +43,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mockito;
 import org.robolectric.Robolectric;
@@ -119,6 +121,10 @@
 
     private ButtonDataImpl getDataForStaticNewTabIconButton() {
         Drawable iconDrawable = AppCompatResources.getDrawable(mActivity, R.drawable.new_tab_icon);
+        return getDataForStaticNewTabIconButton(iconDrawable);
+    }
+
+    private ButtonDataImpl getDataForStaticNewTabIconButton(Drawable iconDrawable) {
         OnClickListener clickListener = mock(OnClickListener.class);
         OnLongClickListener longClickListener = mock(OnLongClickListener.class);
         String contentDescription = mActivity.getString(R.string.button_new_tab);
@@ -728,4 +734,68 @@
         // Action chip shouldn't be visible
         assertEquals(View.GONE, mActionChipLabel.getVisibility());
     }
+
+    @Test
+    public void testUpdateButton_sameVariantUpdatesShouldNotBeAnimated() {
+        ArgumentCaptor<Transition> transitionArgumentCaptor =
+                ArgumentCaptor.forClass(Transition.class);
+        // Create two ButtonData objects for the same variant (NEW_TAB) with different icons.
+        ButtonDataImpl newTabButtonData = getDataForStaticNewTabIconButton(
+                AppCompatResources.getDrawable(mActivity, R.drawable.btn_star));
+        ButtonDataImpl updatedNewTabButtonData = getDataForStaticNewTabIconButton(
+                AppCompatResources.getDrawable(mActivity, R.drawable.btn_star_filled));
+
+        // First show the first icon.
+        mOptionalButtonView.updateButtonWithAnimation(newTabButtonData);
+
+        verify(mMockBeginDelayedTransition).onResult(transitionArgumentCaptor.capture());
+        // Going from no button to a button should be animated.
+        assertNotEquals(0, transitionArgumentCaptor.getValue().getDuration());
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Now show the second icon, with the same variant but a different drawable.
+        mOptionalButtonView.updateButtonWithAnimation(updatedNewTabButtonData);
+
+        verify(mMockBeginDelayedTransition, times(2)).onResult(transitionArgumentCaptor.capture());
+        // Updating the drawable without changing variant should not be animated.
+        assertEquals(0, transitionArgumentCaptor.getValue().getDuration());
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Now hide the button.
+        mOptionalButtonView.updateButtonWithAnimation(null);
+        verify(mMockBeginDelayedTransition, times(3)).onResult(transitionArgumentCaptor.capture());
+        // Hiding the button should be animated.
+        assertNotEquals(0, transitionArgumentCaptor.getValue().getDuration());
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+    }
+
+    @Test
+    public void testUpdateButton_differentVariantUpdatesShouldBeAnimated() {
+        ArgumentCaptor<Transition> transitionArgumentCaptor =
+                ArgumentCaptor.forClass(Transition.class);
+        // Create two ButtonData objects with different variants.
+        ButtonData newTabButtonData = getDataForStaticNewTabIconButton();
+        ButtonData priceTrackingButtonData = getDataForPriceTrackingIconButton();
+
+        // First show the new tab variant.
+        mOptionalButtonView.updateButtonWithAnimation(newTabButtonData);
+
+        verify(mMockBeginDelayedTransition).onResult(transitionArgumentCaptor.capture());
+        // Going from no button to a button should be animated.
+        assertNotEquals(0, transitionArgumentCaptor.getValue().getDuration());
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+
+        // Now show the price tracking button.
+        mOptionalButtonView.updateButtonWithAnimation(priceTrackingButtonData);
+
+        verify(mMockBeginDelayedTransition, times(2)).onResult(transitionArgumentCaptor.capture());
+        // Changing variants should be animated.
+        assertNotEquals(0, transitionArgumentCaptor.getValue().getDuration());
+        mOptionalButtonView.onTransitionStart(null);
+        mOptionalButtonView.onTransitionEnd(null);
+    }
 }
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
index b5234654..f580ace 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.cc
@@ -98,6 +98,12 @@
   GetHoldingSpaceKeyedService(profile_)->AddDiagnosticsLog(file_path);
 }
 
+const std::string& HoldingSpaceClientImpl::AddItemOfType(
+    HoldingSpaceItem::Type type,
+    const base::FilePath& file_path) {
+  return GetHoldingSpaceKeyedService(profile_)->AddItemOfType(type, file_path);
+}
+
 void HoldingSpaceClientImpl::AddScreenCapture(HoldingSpaceItem::Type type,
                                               const base::FilePath& file_path) {
   GetHoldingSpaceKeyedService(profile_)->AddScreenCapture(type, file_path);
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
index 182fbfa..b19cd0c 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl.h
@@ -26,6 +26,8 @@
 
   // HoldingSpaceClient:
   void AddDiagnosticsLog(const base::FilePath& file_path) override;
+  const std::string& AddItemOfType(HoldingSpaceItem::Type type,
+                                   const base::FilePath& file_path) override;
   void AddScreenCapture(HoldingSpaceItem::Type type,
                         const base::FilePath& file_path) override;
   void CopyImageToClipboard(const HoldingSpaceItem&, SuccessCallback) override;
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
index 00b57bf3..9a7df414 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_client_impl_browsertest.cc
@@ -17,6 +17,7 @@
 #include "base/functional/callback_helpers.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/strings/strcat.h"
 #include "base/test/bind.h"
 #include "base/unguessable_token.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
@@ -105,6 +106,39 @@
   EXPECT_EQ(diagnostics_log_item->file_path(), log_path);
 }
 
+// Verifies that `HoldingSpaceClient::AddItemOfType()` works as intended.
+IN_PROC_BROWSER_TEST_F(HoldingSpaceClientImplTest, AddItemOfType) {
+  using Type = HoldingSpaceItem::Type;
+
+  // Verify existence of controller, `client`, and `model`.
+  ASSERT_TRUE(HoldingSpaceController::Get());
+  auto* client = HoldingSpaceController::Get()->client();
+  ASSERT_TRUE(client);
+  auto* model = HoldingSpaceController::Get()->model();
+  ASSERT_TRUE(model);
+
+  // Verify `model` is initially empty.
+  size_t expected_count = 0u;
+  EXPECT_EQ(model->items().size(), expected_count);
+
+  // Verify client API works for every item type.
+  for (size_t i = 0u; i <= static_cast<int>(Type::kMaxValue); ++i) {
+    // Create the item of the `expected_type` using the client API.
+    const HoldingSpaceItem::Type expected_type = static_cast<Type>(i);
+    const base::FilePath expected_file_path =
+        TestFile(GetProfile(), kTextFilePath);
+    const std::string& expected_id =
+        client->AddItemOfType(expected_type, expected_file_path);
+
+    // Verify the item was created as expected.
+    ASSERT_EQ(model->items().size(), ++expected_count);
+    const HoldingSpaceItem* item = model->items().back().get();
+    EXPECT_EQ(item->id(), expected_id);
+    EXPECT_EQ(item->type(), expected_type);
+    EXPECT_EQ(item->file_path(), expected_file_path);
+  }
+}
+
 // Verifies that `HoldingSpaceClient::CopyImageToClipboard()` works as intended
 // when attempting to copy both image backed and non-image backed holding space
 // items.
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
index 4051dd7..82b26b9d 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h
@@ -68,6 +68,7 @@
       mojo::PendingReceiver<crosapi::mojom::HoldingSpaceService> receiver);
 
   // crosapi::mojom::HoldingSpaceKeyedService:
+  // TODO(http://b/274477308): Remove one-off API.
   void AddPrintedPdf(const base::FilePath& printed_pdf_path,
                      bool from_incognito_profile) override;
 
@@ -89,9 +90,11 @@
   // files system URLs as GURLs.
   std::vector<GURL> GetPinnedFiles() const;
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a diagnostics log item backed by the provided absolute file path.
   void AddDiagnosticsLog(const base::FilePath& diagnostics_log_path);
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a download item of the specified `type` backed by the provided
   // absolute file path. Returns the id of the added holding space item or an
   // empty string if the item was not added due to de-duplication checks.
@@ -103,9 +106,11 @@
       HoldingSpaceImage::PlaceholderImageSkiaResolver
           placeholder_image_skia_resolver = base::NullCallback());
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a nearby share item backed by the provided absolute file path.
   void AddNearbyShare(const base::FilePath& nearby_share_path);
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a photo or video downloaded from a connected Android phone via
   // PhoneHub. Returns the id of the added holding space item or an empty string
   // if the item was not added due to de-duplication checks.
@@ -113,9 +118,11 @@
       const base::FilePath& item_path,
       const HoldingSpaceProgress& progress);
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a scanned item backed by the provided absolute file path.
   void AddScan(const base::FilePath& file_path);
 
+  // TODO(http://b/274477308): Remove one-off API.
   // Adds a screen capture item of the specified `type` backed by the provided
   // absolute file path. NOTE: `type` must refer to a screen capture type.
   void AddScreenCapture(HoldingSpaceItem::Type type,
diff --git a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
index 038cbe5..6b082f4 100644
--- a/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
+++ b/chrome/browser/ui/ash/holding_space/holding_space_keyed_service_unittest.cc
@@ -2586,6 +2586,13 @@
         EXPECT_EQ(holding_space_model->ContainsItem(type, file_path),
                   holding_space_service->AddDownload(type, file_path).empty());
         break;
+      case HoldingSpaceItem::Type::kCameraAppPhoto:
+      case HoldingSpaceItem::Type::kCameraAppScanJpg:
+      case HoldingSpaceItem::Type::kCameraAppScanPdf:
+      case HoldingSpaceItem::Type::kCameraAppVideoGif:
+      case HoldingSpaceItem::Type::kCameraAppVideoMp4:
+        holding_space_service->AddItemOfType(type, file_path);
+        break;
       case HoldingSpaceItem::Type::kDiagnosticsLog:
         holding_space_service->AddDiagnosticsLog(file_path);
         break;
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.cc b/chrome/browser/ui/ash/system_tray_client_impl.cc
index cb6d8675..19ea948 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.cc
+++ b/chrome/browser/ui/ash/system_tray_client_impl.cc
@@ -29,6 +29,7 @@
 #include "chrome/browser/apps/app_service/launch_utils.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/ash/crosapi/browser_manager.h"
+#include "chrome/browser/ash/eol_incentive_util.h"
 #include "chrome/browser/ash/login/help_app_launcher.h"
 #include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
 #include "chrome/browser/ash/policy/core/device_cloud_policy_manager_ash.h"
@@ -364,7 +365,9 @@
   system_tray_->SetLocaleList(std::move(locale_list), current_locale_iso_code);
 }
 
-void SystemTrayClientImpl::SetShowEolNotice(bool show) {
+void SystemTrayClientImpl::SetShowEolNotice(bool show,
+                                            bool eol_passed_recently) {
+  eol_incentive_recently_passed_ = eol_passed_recently;
   system_tray_->SetShowEolNotice(show);
 }
 ////////////////////////////////////////////////////////////////////////////////
@@ -784,12 +787,35 @@
 }
 
 void SystemTrayClientImpl::ShowEolInfoPage() {
+  const bool use_offer_url = ash::features::kEolIncentiveParam.Get() !=
+                                 ash::features::EolIncentiveParam::kNoOffer &&
+                             eol_incentive_recently_passed_;
+
+  if (eol_incentive_recently_passed_) {
+    ash::eol_incentive_util::RecordButtonClicked(
+        use_offer_url ? ash::eol_incentive_util::EolIncentiveButtonType::
+                            kQuickSettings_Offer_RecentlyPassed
+                      : ash::eol_incentive_util::EolIncentiveButtonType::
+                            kQuickSettings_NoOffer_RecentlyPassed);
+  } else {
+    DCHECK(!use_offer_url);
+    ash::eol_incentive_util::RecordButtonClicked(
+        ash::eol_incentive_util::EolIncentiveButtonType::
+            kQuickSettings_NoOffer_Passed);
+  }
+
   ash::NewWindowDelegate::GetPrimary()->OpenUrl(
-      GURL(chrome::kEolNotificationURL),
+      GURL(use_offer_url ? chrome::kEolIncentiveNotificationOfferURL
+                         : chrome::kEolIncentiveNotificationNoOfferURL),
       ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
       ash::NewWindowDelegate::Disposition::kNewForegroundTab);
 }
 
+void SystemTrayClientImpl::RecordEolNoticeShown() {
+  ash::eol_incentive_util::RecordShowSourceHistogram(
+      ash::eol_incentive_util::EolIncentiveShowSource::kQuickSettings);
+}
+
 bool SystemTrayClientImpl::IsUserFeedbackEnabled() {
   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
           ash::switches::kForceShowReleaseTrack)) {
diff --git a/chrome/browser/ui/ash/system_tray_client_impl.h b/chrome/browser/ui/ash/system_tray_client_impl.h
index c652e83..1f0169ec 100644
--- a/chrome/browser/ui/ash/system_tray_client_impl.h
+++ b/chrome/browser/ui/ash/system_tray_client_impl.h
@@ -56,7 +56,7 @@
   void SetPerformanceTracingIconVisible(bool visible);
   void SetLocaleList(std::vector<ash::LocaleInfo> locale_list,
                      const std::string& current_locale_iso_code);
-  void SetShowEolNotice(bool show);
+  void SetShowEolNotice(bool show, bool eol_passed_recently);
 
   // ash::SystemTrayClient:
   void ShowSettings(int64_t display_id) override;
@@ -107,6 +107,7 @@
   void ShowChannelInfoGiveFeedback() override;
   void ShowAudioSettings() override;
   void ShowEolInfoPage() override;
+  void RecordEolNoticeShown() override;
   bool IsUserFeedbackEnabled() override;
 
  protected:
@@ -150,6 +151,9 @@
   std::string last_enterprise_account_domain_manager_;
 
   std::unique_ptr<EnterpriseAccountObserver> enterprise_account_observer_;
+
+  // Whether the eol incentive is due to a recently pasesed end of life date.
+  bool eol_incentive_recently_passed_ = false;
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_SYSTEM_TRAY_CLIENT_IMPL_H_
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h b/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h
index fc033f5..cb6618a 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h
@@ -6,17 +6,13 @@
 #define CHROME_BROWSER_UI_COCOA_APPLESCRIPT_BOOKMARK_APPLESCRIPT_TEST_UTILS_H_
 
 #import <Foundation/Foundation.h>
-#import <objc/objc-runtime.h>
 
 #include "base/mac/scoped_nsobject.h"
 #import "chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h"
 #include "chrome/test/base/in_process_browser_test.h"
 
 // Used to emulate an active running script, useful for testing purposes.
-@interface FakeScriptCommand : NSScriptCommand {
-  Method _originalMethod;
-  Method _alternateMethod;
-}
+@interface FakeScriptCommand : NSScriptCommand
 @end
 
 // The base class for all our bookmark related unit tests.
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm b/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm
index 9d623ed..2268db2 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.mm
@@ -4,36 +4,37 @@
 
 #import "chrome/browser/ui/cocoa/applescript/bookmark_applescript_test_utils.h"
 
+#include "base/mac/scoped_objc_class_swizzler.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/test/bookmark_test_helpers.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 // Represents the current fake command that is executing.
-static FakeScriptCommand* kFakeCurrentCommand;
+static FakeScriptCommand* gFakeCurrentCommand;
 
-@implementation FakeScriptCommand
+@implementation FakeScriptCommand {
+  absl::optional<base::mac::ScopedObjCClassSwizzler> swizzler;
+}
 
 - (instancetype)init {
   if ((self = [super init])) {
-    _originalMethod = class_getClassMethod([NSScriptCommand class],
-                                           @selector(currentCommand));
-    _alternateMethod =
-        class_getClassMethod([self class], @selector(currentCommand));
-    method_exchangeImplementations(_originalMethod, _alternateMethod);
-    kFakeCurrentCommand = self;
+    swizzler.emplace([NSScriptCommand class], [FakeScriptCommand class],
+                     @selector(currentCommand));
+    gFakeCurrentCommand = self;
   }
   return self;
 }
 
 + (NSScriptCommand*)currentCommand {
-  return kFakeCurrentCommand;
+  return gFakeCurrentCommand;
 }
 
 - (void)dealloc {
-  method_exchangeImplementations(_originalMethod, _alternateMethod);
-  kFakeCurrentCommand = nil;
+  swizzler.reset();
+  gFakeCurrentCommand = nil;
   [super dealloc];
 }
 
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h
index db18596..f824c8dd 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.h
@@ -17,15 +17,16 @@
 // Bookmark folder manipulation methods.
 // Returns an array of |BookmarkFolderAppleScript*| of all the bookmark folders
 // contained within this particular folder.
-- (NSArray*)bookmarkFolders;
+@property(readonly) NSArray* bookmarkFolders;
 
 // Inserts a bookmark folder at the end.
-- (void)insertInBookmarkFolders:(id)aBookmarkFolder;
+- (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)aBookmarkFolder;
 
 // Inserts a bookmark folder at some position in the list.
 // Called by AppleScript which takes care of bounds checking, make sure of it
 // before calling directly.
-- (void)insertInBookmarkFolders:(id)aBookmarkFolder atIndex:(size_t)index;
+- (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)aBookmarkFolder
+                        atIndex:(size_t)index;
 
 // Remove a bookmark folder from the list.
 // Called by AppleScript which takes care of bounds checking, make sure of it
@@ -35,7 +36,7 @@
 // Bookmark item manipulation methods.
 // Returns an array of |BookmarkItemAppleScript*| of all the bookmark items
 // contained within this particular folder.
-- (NSArray*)bookmarkItems;
+@property(readonly) NSArray* bookmarkItems;
 
 // Inserts a bookmark item at the end.
 - (void)insertInBookmarkItems:(BookmarkItemAppleScript*)aBookmarkItem;
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.mm b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.mm
index 2aabbcab..3d6c6b7 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript.mm
@@ -20,11 +20,13 @@
 
 - (NSArray*)bookmarkFolders {
   NSMutableArray* bookmarkFolders =
-      [NSMutableArray arrayWithCapacity:[self bookmarkNode]->children().size()];
+      [NSMutableArray arrayWithCapacity:self.bookmarkNode->children().size()];
 
-  for (const auto& node : [self bookmarkNode]->children()) {
-    if (!node->is_folder())
+  for (const auto& node : self.bookmarkNode->children()) {
+    if (!node->is_folder()) {
       continue;
+    }
+
     base::scoped_nsobject<BookmarkFolderAppleScript> bookmarkFolder(
         [[BookmarkFolderAppleScript alloc] initWithBookmarkNode:node.get()]);
     [bookmarkFolder setContainer:self
@@ -35,65 +37,71 @@
   return bookmarkFolders;
 }
 
-- (void)insertInBookmarkFolders:(id)aBookmarkFolder {
+- (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)aBookmarkFolder {
   // This method gets called when a new bookmark folder is created so
   // the container and property are set here.
   [aBookmarkFolder setContainer:self
                        property:AppleScript::kBookmarkFoldersProperty];
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
-  const BookmarkNode* node = model->AddFolder(
-      [self bookmarkNode], [self bookmarkNode]->children().size(),
-      std::u16string());
+  const BookmarkNode* node =
+      model->AddFolder(self.bookmarkNode, self.bookmarkNode->children().size(),
+                       std::u16string());
   if (!node) {
     AppleScript::SetError(AppleScript::Error::kCreateBookmarkFolder);
     return;
   }
 
-  [aBookmarkFolder setBookmarkNode:node];
+  aBookmarkFolder.bookmarkNode = node;
 }
 
-- (void)insertInBookmarkFolders:(id)aBookmarkFolder atIndex:(size_t)index {
+- (void)insertInBookmarkFolders:(BookmarkFolderAppleScript*)aBookmarkFolder
+                        atIndex:(size_t)index {
   // This method gets called when a new bookmark folder is created so
   // the container and property are set here.
   [aBookmarkFolder setContainer:self
                        property:AppleScript::kBookmarkFoldersProperty];
   size_t position = [self calculatePositionOfBookmarkFolderAt:index];
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
   const BookmarkNode* node =
-      model->AddFolder([self bookmarkNode], position, std::u16string());
+      model->AddFolder(self.bookmarkNode, position, std::u16string());
   if (!node) {
     AppleScript::SetError(AppleScript::Error::kCreateBookmarkFolder);
     return;
   }
 
-  [aBookmarkFolder setBookmarkNode:node];
+  aBookmarkFolder.bookmarkNode = node;
 }
 
 - (void)removeFromBookmarkFoldersAtIndex:(size_t)index {
   size_t position = [self calculatePositionOfBookmarkFolderAt:index];
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
-  model->Remove([self bookmarkNode]->children()[position].get(),
+  model->Remove(self.bookmarkNode->children()[position].get(),
                 bookmarks::metrics::BookmarkEditSource::kUser);
 }
 
 - (NSArray*)bookmarkItems {
   NSMutableArray* bookmarkItems =
-      [NSMutableArray arrayWithCapacity:[self bookmarkNode]->children().size()];
+      [NSMutableArray arrayWithCapacity:self.bookmarkNode->children().size()];
 
-  for (const auto& node : [self bookmarkNode]->children()) {
-    if (!node->is_url())
+  for (const auto& node : self.bookmarkNode->children()) {
+    if (!node->is_url()) {
       continue;
+    }
+
     base::scoped_nsobject<BookmarkItemAppleScript> bookmarkItem(
         [[BookmarkItemAppleScript alloc] initWithBookmarkNode:node.get()]);
     [bookmarkItem setContainer:self
@@ -110,25 +118,26 @@
   [aBookmarkItem setContainer:self
                      property:AppleScript::kBookmarkItemsProperty];
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
-  GURL url = GURL(base::SysNSStringToUTF8([aBookmarkItem URL]));
+  GURL url = GURL(base::SysNSStringToUTF8(aBookmarkItem.URL));
   if (!url.is_valid()) {
     AppleScript::SetError(AppleScript::Error::kInvalidURL);
     return;
   }
 
-  const BookmarkNode* node = model->AddNewURL(
-      [self bookmarkNode], [self bookmarkNode]->children().size(),
-      std::u16string(), url);
+  const BookmarkNode* node =
+      model->AddNewURL(self.bookmarkNode, self.bookmarkNode->children().size(),
+                       std::u16string(), url);
   if (!node) {
     AppleScript::SetError(AppleScript::Error::kCreateBookmarkItem);
     return;
   }
 
-  [aBookmarkItem setBookmarkNode:node];
+  aBookmarkItem.bookmarkNode = node;
 }
 
 - (void)insertInBookmarkItems:(BookmarkItemAppleScript*)aBookmarkItem
@@ -139,34 +148,36 @@
                      property:AppleScript::kBookmarkItemsProperty];
   size_t position = [self calculatePositionOfBookmarkItemAt:index];
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
-  GURL url(base::SysNSStringToUTF8([aBookmarkItem URL]));
+  GURL url(base::SysNSStringToUTF8(aBookmarkItem.URL));
   if (!url.is_valid()) {
     AppleScript::SetError(AppleScript::Error::kInvalidURL);
     return;
   }
 
   const BookmarkNode* node =
-      model->AddNewURL([self bookmarkNode], position, std::u16string(), url);
+      model->AddNewURL(self.bookmarkNode, position, std::u16string(), url);
   if (!node) {
     AppleScript::SetError(AppleScript::Error::kCreateBookmarkItem);
     return;
   }
 
-  [aBookmarkItem setBookmarkNode:node];
+  aBookmarkItem.bookmarkNode = node;
 }
 
 - (void)removeFromBookmarkItemsAtIndex:(size_t)index {
   size_t position = [self calculatePositionOfBookmarkItemAt:index];
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
-  model->Remove([self bookmarkNode]->children()[position].get(),
+  model->Remove(self.bookmarkNode->children()[position].get(),
                 bookmarks::metrics::BookmarkEditSource::kUser);
 }
 
@@ -177,7 +188,7 @@
   ++index;
   size_t count = 0;
   while (index) {
-    if ([self bookmarkNode]->children()[count++] -> is_folder()) {
+    if (self.bookmarkNode->children()[count++]->is_folder()) {
       --index;
     }
   }
@@ -191,7 +202,7 @@
   ++index;
   size_t count = 0;
   while (index) {
-    if ([self bookmarkNode]->children()[count++] -> is_url()) {
+    if (self.bookmarkNode->children()[count++]->is_url()) {
       --index;
     }
   }
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm
index 871fc9b..b3cc0bc5 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_folder_applescript_browsertest.mm
@@ -25,20 +25,20 @@
 
 // Test all the bookmark folders within.
 IN_PROC_BROWSER_TEST_F(BookmarkFolderAppleScriptTest, BookmarkFolders) {
-  NSArray* bookmark_folders = [bookmark_bar_.get() bookmarkFolders];
+  NSArray* bookmark_folders = bookmark_bar_.get().bookmarkFolders;
 
-  EXPECT_EQ(2U, [bookmark_folders count]);
+  EXPECT_EQ(2U, bookmark_folders.count);
 
   BookmarkFolderAppleScript* f1 = bookmark_folders[0];
   BookmarkFolderAppleScript* f2 = bookmark_folders[1];
-  EXPECT_NSEQ(@"f1", [f1 title]);
-  EXPECT_NSEQ(@"f2", [f2 title]);
-  EXPECT_EQ(2, [[f1 index] intValue]);
-  EXPECT_EQ(4, [[f2 index] intValue]);
+  EXPECT_NSEQ(@"f1", f1.title);
+  EXPECT_NSEQ(@"f2", f2.title);
+  EXPECT_EQ(2, f1.index.intValue);
+  EXPECT_EQ(4, f2.index.intValue);
 
   for (BookmarkFolderAppleScript* bookmark_folder in bookmark_folders) {
-    EXPECT_EQ([bookmark_folder container], bookmark_bar_.get());
-    EXPECT_NSEQ(kBookmarkFoldersProperty, [bookmark_folder containerProperty]);
+    EXPECT_EQ(bookmark_folder.container, bookmark_bar_.get());
+    EXPECT_NSEQ(kBookmarkFoldersProperty, bookmark_folder.containerProperty);
   }
 }
 
@@ -49,16 +49,17 @@
   // properties {title:"foo"}|.
   base::scoped_nsobject<BookmarkFolderAppleScript> bookmark_folder(
       [[BookmarkFolderAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[bookmark_folder.get() uniqueID] copy]);
+  base::scoped_nsobject<NSString> unique_id(
+      [bookmark_folder.get().uniqueID copy]);
   [bookmark_folder.get() setTitle:@"foo"];
   [bookmark_bar_.get() insertInBookmarkFolders:bookmark_folder.get()];
 
   // Represents the bookmark folder after it's added.
-  BookmarkFolderAppleScript* bf = [bookmark_bar_.get() bookmarkFolders][2];
-  EXPECT_NSEQ(@"foo", [bf title]);
-  EXPECT_EQ([bf container], bookmark_bar_.get());
-  EXPECT_NSEQ(kBookmarkFoldersProperty, [bf containerProperty]);
-  EXPECT_NSEQ(var.get(), [bf uniqueID]);
+  BookmarkFolderAppleScript* bf = bookmark_bar_.get().bookmarkFolders[2];
+  EXPECT_NSEQ(@"foo", bf.title);
+  EXPECT_EQ(bf.container, bookmark_bar_.get());
+  EXPECT_NSEQ(kBookmarkFoldersProperty, bf.containerProperty);
+  EXPECT_NSEQ(unique_id.get(), bf.uniqueID);
 }
 
 // Insert a new bookmark folder at a particular position.
@@ -69,47 +70,48 @@
   // properties {title:"foo"} at after bookmark folder 1|.
   base::scoped_nsobject<BookmarkFolderAppleScript> bookmark_folder(
       [[BookmarkFolderAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[bookmark_folder.get() uniqueID] copy]);
-  [bookmark_folder.get() setTitle:@"foo"];
-  [bookmark_bar_.get() insertInBookmarkFolders:bookmark_folder.get() atIndex:1];
+  base::scoped_nsobject<NSString> unique_id(
+      [bookmark_folder.get().uniqueID copy]);
+  bookmark_folder.get().title = @"foo";
+  [bookmark_bar_ insertInBookmarkFolders:bookmark_folder atIndex:1];
 
   // Represents the bookmark folder after it's added.
-  BookmarkFolderAppleScript* bf = [bookmark_bar_.get() bookmarkFolders][1];
-  EXPECT_NSEQ(@"foo", [bf title]);
-  EXPECT_EQ([bf container], bookmark_bar_.get());
-  EXPECT_NSEQ(kBookmarkFoldersProperty, [bf containerProperty]);
-  EXPECT_NSEQ(var.get(), [bf uniqueID]);
+  BookmarkFolderAppleScript* bf = bookmark_bar_.get().bookmarkFolders[1];
+  EXPECT_NSEQ(@"foo", bf.title);
+  EXPECT_EQ(bf.container, bookmark_bar_.get());
+  EXPECT_NSEQ(kBookmarkFoldersProperty, bf.containerProperty);
+  EXPECT_NSEQ(unique_id, bf.uniqueID);
 }
 
 // Delete bookmark folders.
 IN_PROC_BROWSER_TEST_F(BookmarkFolderAppleScriptTest, DeleteBookmarkFolders) {
   unsigned int folder_count = 2, item_count = 3;
   for (unsigned int i = 0; i < folder_count; ++i) {
-    EXPECT_EQ(folder_count - i, [[bookmark_bar_.get() bookmarkFolders] count]);
-    EXPECT_EQ(item_count, [[bookmark_bar_.get() bookmarkItems] count]);
-    [bookmark_bar_.get() removeFromBookmarkFoldersAtIndex:0];
+    EXPECT_EQ(folder_count - i, bookmark_bar_.get().bookmarkFolders.count);
+    EXPECT_EQ(item_count, bookmark_bar_.get().bookmarkItems.count);
+    [bookmark_bar_ removeFromBookmarkFoldersAtIndex:0];
   }
 }
 
 // Test all the bookmark items within.
 IN_PROC_BROWSER_TEST_F(BookmarkFolderAppleScriptTest, BookmarkItems) {
-  NSArray* bookmark_items = [bookmark_bar_.get() bookmarkItems];
+  NSArray* bookmark_items = bookmark_bar_.get().bookmarkItems;
 
-  EXPECT_EQ(3U, [bookmark_items count]);
+  EXPECT_EQ(3U, bookmark_items.count);
 
   BookmarkItemAppleScript* i1 = bookmark_items[0];
   BookmarkItemAppleScript* i2 = bookmark_items[1];
   BookmarkItemAppleScript* i3 = bookmark_items[2];
-  EXPECT_NSEQ(@"a", [i1 title]);
-  EXPECT_NSEQ(@"d", [i2 title]);
-  EXPECT_NSEQ(@"h", [i3 title]);
-  EXPECT_EQ(1, [[i1 index] intValue]);
-  EXPECT_EQ(3, [[i2 index] intValue]);
-  EXPECT_EQ(5, [[i3 index] intValue]);
+  EXPECT_NSEQ(@"a", i1.title);
+  EXPECT_NSEQ(@"d", i2.title);
+  EXPECT_NSEQ(@"h", i3.title);
+  EXPECT_EQ(1, i1.index.intValue);
+  EXPECT_EQ(3, i2.index.intValue);
+  EXPECT_EQ(5, i3.index.intValue);
 
   for (BookmarkItemAppleScript* bookmark_item in bookmark_items) {
-    EXPECT_EQ([bookmark_item container], bookmark_bar_.get());
-    EXPECT_NSEQ(kBookmarkItemsProperty, [bookmark_item containerProperty]);
+    EXPECT_EQ(bookmark_item.container, bookmark_bar_.get());
+    EXPECT_NSEQ(kBookmarkItemsProperty, bookmark_item.containerProperty);
   }
 }
 
@@ -120,27 +122,27 @@
   // properties {title:"Google", URL:"http://google.com"}|.
   base::scoped_nsobject<BookmarkItemAppleScript> bookmark_item(
       [[BookmarkItemAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[bookmark_item.get() uniqueID] copy]);
-  [bookmark_item.get() setTitle:@"Google"];
-  [bookmark_item.get() setURL:@"http://google.com"];
-  [bookmark_bar_.get() insertInBookmarkItems:bookmark_item.get()];
+  base::scoped_nsobject<NSString> unique_id(
+      [bookmark_item.get().uniqueID copy]);
+  bookmark_item.get().title = @"Google";
+  bookmark_item.get().URL = @"http://google.com";
+  [bookmark_bar_ insertInBookmarkItems:bookmark_item];
 
   // Represents the bookmark item after it's added.
-  BookmarkItemAppleScript* bi = [bookmark_bar_.get() bookmarkItems][3];
-  EXPECT_NSEQ(@"Google", [bi title]);
-  EXPECT_EQ(GURL("http://google.com/"),
-            GURL(base::SysNSStringToUTF8([bi URL])));
-  EXPECT_EQ([bi container], bookmark_bar_.get());
-  EXPECT_NSEQ(kBookmarkItemsProperty, [bi containerProperty]);
-  EXPECT_NSEQ(var.get(), [bi uniqueID]);
+  BookmarkItemAppleScript* bi = bookmark_bar_.get().bookmarkItems[3];
+  EXPECT_NSEQ(@"Google", bi.title);
+  EXPECT_EQ(GURL("http://google.com/"), GURL(base::SysNSStringToUTF8(bi.URL)));
+  EXPECT_EQ(bi.container, bookmark_bar_.get());
+  EXPECT_NSEQ(kBookmarkItemsProperty, bi.containerProperty);
+  EXPECT_NSEQ(unique_id.get(), bi.uniqueID);
 
   // Test to see no bookmark item is created when no/invalid URL is entered.
   base::scoped_nsobject<FakeScriptCommand> fakeScriptCommand(
       [[FakeScriptCommand alloc] init]);
   bookmark_item.reset([[BookmarkItemAppleScript alloc] init]);
-  [bookmark_bar_.get() insertInBookmarkItems:bookmark_item.get()];
+  [bookmark_bar_ insertInBookmarkItems:bookmark_item];
   EXPECT_EQ(static_cast<int>(Error::kInvalidURL),
-            [fakeScriptCommand.get() scriptErrorNumber]);
+            fakeScriptCommand.get().scriptErrorNumber);
 }
 
 // Insert a new bookmark item at a particular position.
@@ -153,46 +155,46 @@
   //       {title:"XKCD", URL:"http://xkcd.org} at after bookmark item 1
   base::scoped_nsobject<BookmarkItemAppleScript> bookmark_item(
       [[BookmarkItemAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[bookmark_item.get() uniqueID] copy]);
-  [bookmark_item.get() setTitle:@"XKCD"];
-  [bookmark_item.get() setURL:@"http://xkcd.org"];
+  base::scoped_nsobject<NSString> unique_id(
+      [bookmark_item.get().uniqueID copy]);
+  bookmark_item.get().title = @"XKCD";
+  bookmark_item.get().URL = @"http://xkcd.org";
 
-  [bookmark_bar_.get() insertInBookmarkItems:bookmark_item.get() atIndex:1];
+  [bookmark_bar_ insertInBookmarkItems:bookmark_item atIndex:1];
 
   // Represents the bookmark item after its added.
-  BookmarkItemAppleScript* bi = [bookmark_bar_.get() bookmarkItems][1];
-  EXPECT_NSEQ(@"XKCD", [bi title]);
-  EXPECT_EQ(GURL("http://xkcd.org/"),
-            GURL(base::SysNSStringToUTF8([bi URL])));
-  EXPECT_EQ([bi container], bookmark_bar_.get());
-  EXPECT_NSEQ(kBookmarkItemsProperty, [bi containerProperty]);
-  EXPECT_NSEQ(var.get(), [bi uniqueID]);
+  BookmarkItemAppleScript* bi = bookmark_bar_.get().bookmarkItems[1];
+  EXPECT_NSEQ(@"XKCD", bi.title);
+  EXPECT_EQ(GURL("http://xkcd.org/"), GURL(base::SysNSStringToUTF8(bi.URL)));
+  EXPECT_EQ(bi.container, bookmark_bar_.get());
+  EXPECT_NSEQ(kBookmarkItemsProperty, bi.containerProperty);
+  EXPECT_NSEQ(unique_id, bi.uniqueID);
 
   // Test to see no bookmark item is created when no/invalid URL is entered.
   base::scoped_nsobject<FakeScriptCommand> fakeScriptCommand(
       [[FakeScriptCommand alloc] init]);
   bookmark_item.reset([[BookmarkItemAppleScript alloc] init]);
-  [bookmark_bar_.get() insertInBookmarkItems:bookmark_item.get() atIndex:1];
+  [bookmark_bar_ insertInBookmarkItems:bookmark_item atIndex:1];
   EXPECT_EQ(static_cast<int>(Error::kInvalidURL),
-            [fakeScriptCommand.get() scriptErrorNumber]);
+            fakeScriptCommand.get().scriptErrorNumber);
 }
 
 // Delete bookmark items.
 IN_PROC_BROWSER_TEST_F(BookmarkFolderAppleScriptTest, DeleteBookmarkItems) {
   unsigned int folder_count = 2, item_count = 3;
   for (unsigned int i = 0; i < item_count; ++i) {
-    EXPECT_EQ(folder_count, [[bookmark_bar_.get() bookmarkFolders] count]);
-    EXPECT_EQ(item_count - i, [[bookmark_bar_.get() bookmarkItems] count]);
-    [bookmark_bar_.get() removeFromBookmarkItemsAtIndex:0];
+    EXPECT_EQ(folder_count, bookmark_bar_.get().bookmarkFolders.count);
+    EXPECT_EQ(item_count - i, bookmark_bar_.get().bookmarkItems.count);
+    [bookmark_bar_ removeFromBookmarkItemsAtIndex:0];
   }
 }
 
 // Set and get title.
 IN_PROC_BROWSER_TEST_F(BookmarkFolderAppleScriptTest, GetAndSetTitle) {
-  NSArray* bookmark_folders = [bookmark_bar_.get() bookmarkFolders];
+  NSArray* bookmark_folders = bookmark_bar_.get().bookmarkFolders;
   BookmarkFolderAppleScript* folder1 = bookmark_folders[0];
-  [folder1 setTitle:@"Foo"];
-  EXPECT_NSEQ(@"Foo", [folder1 title]);
+  folder1.title = @"Foo";
+  EXPECT_NSEQ(@"Foo", folder1.title);
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.h b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.h
index f3b76e20..65a0933a 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.h
@@ -15,12 +15,8 @@
 // Assigns a node, sets its unique ID and also copies temporary values.
 - (void)setBookmarkNode:(const bookmarks::BookmarkNode*)aBookmarkNode;
 
-// Returns the URL that the bookmark item holds.
-- (NSString*)URL;
-
-// Sets the URL of the bookmark item, displays error in AppleScript console
-// if URL is invalid.
-- (void)setURL:(NSString*)aURL;
+// Returns/sets the URL that the bookmark item holds.
+@property(copy) NSString* URL;
 
 @end
 
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.mm b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.mm
index 86c10603..e78f9e4 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript.mm
@@ -16,11 +16,13 @@
 using bookmarks::BookmarkNode;
 
 @interface BookmarkItemAppleScript ()
+
 // Contains the temporary URL when a user creates a new item with the URL
 // specified like:
 //
 //   make new bookmarks item with properties {URL:"foo"}
 @property(nonatomic, copy) NSString* tempURL;
+
 @end
 
 @implementation BookmarkItemAppleScript
@@ -40,24 +42,24 @@
 }
 
 - (void)setBookmarkNode:(const BookmarkNode*)aBookmarkNode {
-  [super setBookmarkNode:aBookmarkNode];
-  [self setURL:self.tempURL];
+  super.bookmarkNode = aBookmarkNode;
+  self.URL = self.tempURL;
 }
 
 - (NSString*)URL {
-  if (![self bookmarkNode]) {
+  if (!self.bookmarkNode) {
     return _tempURL;
   }
 
-  return base::SysUTF8ToNSString([self bookmarkNode]->url().spec());
+  return base::SysUTF8ToNSString(self.bookmarkNode->url().spec());
 }
 
 - (void)setURL:(NSString*)aURL {
   GURL url(base::SysNSStringToUTF8(aURL));
 
   AppController* appDelegate =
-      base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
-  if (!chrome::mac::IsJavaScriptEnabledForProfile([appDelegate lastProfile]) &&
+      base::mac::ObjCCastStrict<AppController>(NSApp.delegate);
+  if (!chrome::mac::IsJavaScriptEnabledForProfile(appDelegate.lastProfile) &&
       url.SchemeIs(url::kJavaScriptScheme)) {
     AppleScript::SetError(AppleScript::Error::kJavaScriptUnsupported);
     return;
@@ -65,12 +67,12 @@
 
   // If a scripter sets a URL before the node is added, the URL is saved at a
   // temporary location.
-  if (![self bookmarkNode]) {
-    [self setTempURL:aURL];
+  if (!self.bookmarkNode) {
+    self.tempURL = aURL;
     return;
   }
 
-  BookmarkModel* model = [self bookmarkModel];
+  BookmarkModel* model = self.bookmarkModel;
   if (!model) {
     return;
   }
@@ -80,7 +82,7 @@
     return;
   }
 
-  model->SetURL([self bookmarkNode], url,
+  model->SetURL(self.bookmarkNode, url,
                 bookmarks::metrics::BookmarkEditSource::kOther);
 }
 
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm
index cb7738a..2ba8007 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_item_applescript_browsertest.mm
@@ -26,24 +26,24 @@
 
 // Set and get title.
 IN_PROC_BROWSER_TEST_F(BookmarkItemAppleScriptTest, GetAndSetTitle) {
-  NSArray* bookmark_items = [bookmark_bar_.get() bookmarkItems];
+  NSArray* bookmark_items = bookmark_bar_.get().bookmarkItems;
   BookmarkItemAppleScript* item1 = bookmark_items[0];
-  [item1 setTitle:@"Foo"];
-  EXPECT_NSEQ(@"Foo", [item1 title]);
+  item1.title = @"Foo";
+  EXPECT_NSEQ(@"Foo", item1.title);
 }
 
 // Set and get URL.
 IN_PROC_BROWSER_TEST_F(BookmarkItemAppleScriptTest, GetAndSetURL) {
-  NSArray* bookmark_items = [bookmark_bar_.get() bookmarkItems];
+  NSArray* bookmark_items = bookmark_bar_.get().bookmarkItems;
   BookmarkItemAppleScript* item1 = bookmark_items[0];
-  [item1 setURL:@"http://foo-bar.org"];
+  item1.URL = @"http://foo-bar.org";
   EXPECT_EQ(GURL("http://foo-bar.org"),
-            GURL(base::SysNSStringToUTF8([item1 URL])));
+            GURL(base::SysNSStringToUTF8(item1.URL)));
 
   // If scripter enters invalid URL.
   base::scoped_nsobject<FakeScriptCommand> fake_script_command(
       [[FakeScriptCommand alloc] init]);
-  [item1 setURL:@"invalid-url.org"];
+  item1.URL = @"invalid-url.org";
   EXPECT_EQ(static_cast<int>(Error::kInvalidURL),
             fake_script_command.get().scriptErrorNumber);
 }
@@ -53,19 +53,19 @@
   PrefService* prefs = profile()->GetPrefs();
   prefs->SetBoolean(prefs::kAllowJavascriptAppleEvents, false);
 
-  NSArray* bookmark_items = [bookmark_bar_.get() bookmarkItems];
+  NSArray* bookmark_items = bookmark_bar_.get().bookmarkItems;
   BookmarkItemAppleScript* item1 = bookmark_items[0];
 
   base::scoped_nsobject<FakeScriptCommand> fake_script_command(
       [[FakeScriptCommand alloc] init]);
-  [item1 setURL:@"javascript:alert('hi');"];
+  item1.URL = @"javascript:alert('hi');";
   EXPECT_EQ(static_cast<int>(Error::kJavaScriptUnsupported),
             fake_script_command.get().scriptErrorNumber);
 
   prefs->SetBoolean(prefs::kAllowJavascriptAppleEvents, true);
-  [item1 setURL:@"javascript:alert('hi');"];
+  item1.URL = @"javascript:alert('hi');";
   EXPECT_EQ(GURL("javascript:alert('hi');"),
-            GURL(base::SysNSStringToUTF8([item1 URL])));
+            GURL(base::SysNSStringToUTF8(item1.URL)));
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.h b/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.h
index e348959..60d3e29 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.h
@@ -27,18 +27,17 @@
     (const bookmarks::BookmarkNode*)aBookmarkNode;
 
 // Assigns/gets a node, sets its unique ID and also copies temporary values.
-- (void)setBookmarkNode:(const bookmarks::BookmarkNode*)aBookmarkNode;
-- (const bookmarks::BookmarkNode*)bookmarkNode;
+@property(assign) const bookmarks::BookmarkNode* bookmarkNode;
 
 // Get and Set title.
-- (NSString*)title;
-- (void)setTitle:(NSString*)aTitle;
+@property(copy) NSString* title;
 
-// Returns the index with respect to its parent bookmark folder.
-- (NSNumber*)index;
+// Returns the index with respect to its parent bookmark folder. 1-based because
+// this is intended for use by AppleScript.
+@property(readonly) NSNumber* index;
 
-// Returns the bookmark model of the browser, returns NULL if there is an error.
-- (bookmarks::BookmarkModel*)bookmarkModel;
+// Returns the bookmark model of the browser, returns null if there is an error.
+@property(readonly) bookmarks::BookmarkModel* bookmarkModel;
 
 @end
 
diff --git a/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.mm b/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.mm
index 820e5033..fd0b4d7 100644
--- a/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/bookmark_node_applescript.mm
@@ -22,11 +22,13 @@
 using bookmarks::BookmarkNode;
 
 @interface BookmarkNodeAppleScript ()
+
 // Contains the temporary title when a user creates a new item with the title
 // specified like:
 //
 //   make new bookmark folder with properties {title:"foo"}
 @property (nonatomic, copy) NSString* tempTitle;
+
 @end
 
 @implementation BookmarkNodeAppleScript {
@@ -37,7 +39,7 @@
 
 - (instancetype)init {
   if ((self = [super init])) {
-    BookmarkModel* model = [self bookmarkModel];
+    BookmarkModel* model = self.bookmarkModel;
     if (!model) {
       [self release];
       return nil;
@@ -90,8 +92,9 @@
 }
 
 - (NSString*)title {
-  if (!_bookmarkNode)
+  if (!_bookmarkNode) {
     return _tempTitle;
+  }
 
   return base::SysUTF16ToNSString(_bookmarkNode->GetTitle());
 }
@@ -103,13 +106,14 @@
   //
   // the node has not yet been created so title is stored in the temp title.
   if (!_bookmarkNode) {
-    [self setTempTitle:aTitle];
+    self.tempTitle = aTitle;
     return;
   }
 
-  BookmarkModel* model = [self bookmarkModel];
-  if (!model)
+  BookmarkModel* model = self.bookmarkModel;
+  if (!model) {
     return;
+  }
 
   model->SetTitle(_bookmarkNode, base::SysNSStringToUTF16(aTitle),
                   bookmarks::metrics::BookmarkEditSource::kOther);
@@ -126,7 +130,7 @@
   AppController* appDelegate =
       base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
 
-  Profile* lastProfile = [appDelegate lastProfile];
+  Profile* lastProfile = appDelegate.lastProfile;
   if (!lastProfile) {
     AppleScript::SetError(AppleScript::Error::kGetProfile);
     return nullptr;
diff --git a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.h b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.h
index 9133391..2135613e 100644
--- a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.h
@@ -18,7 +18,7 @@
 // Application window manipulation methods.
 // Returns an array of |WindowAppleScript*| of all windows present in the
 // application.
-- (NSArray*)appleScriptWindows;
+@property(readonly) NSArray* appleScriptWindows;
 
 // Inserts a window at the beginning.
 - (void)insertInAppleScriptWindows:(WindowAppleScript*)aWindow;
@@ -35,23 +35,23 @@
 - (void)removeFromAppleScriptWindowsAtIndex:(int)index;
 
 // Always returns nil to indicate that it is the root container object.
-- (NSScriptObjectSpecifier*)objectSpecifier;
+@property(readonly) NSScriptObjectSpecifier* objectSpecifier;
 
 // Returns the other bookmarks bookmark folder,
 // returns nil if there is an error.
-- (BookmarkFolderAppleScript*)otherBookmarks;
+@property(readonly) BookmarkFolderAppleScript* otherBookmarks;
 
 // Returns the bookmarks bar bookmark folder, return nil if there is an error.
-- (BookmarkFolderAppleScript*)bookmarksBar;
+@property(readonly) BookmarkFolderAppleScript* bookmarksBar;
 
-// Returns the Bookmarks Bar and Other Bookmarks Folders, each is of type
-// |BookmarkFolderAppleScript*|.
-- (NSArray*)bookmarkFolders;
+// Returns the Bookmarks Bar and Other Bookmarks Folders.
+@property(readonly) NSArray<BookmarkFolderAppleScript*>* bookmarkFolders;
 
 // Required functions, even though bookmarkFolders is declared as
 // read-only, cocoa scripting does not currently prevent writing.
-- (void)insertInBookmarksFolders:(id)aBookmarkFolder;
-- (void)insertInBookmarksFolders:(id)aBookmarkFolder atIndex:(int)index;
+- (void)insertInBookmarksFolders:(BookmarkFolderAppleScript*)aBookmarkFolder;
+- (void)insertInBookmarksFolders:(BookmarkFolderAppleScript*)aBookmarkFolder
+                         atIndex:(int)index;
 - (void)removeFromBookmarksFoldersAtIndex:(int)index;
 
 @end
diff --git a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.mm b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.mm
index 205dde95..58627ef 100644
--- a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript.mm
@@ -68,11 +68,11 @@
   [aWindow setContainer:self property:AppleScript::kWindowsProperty];
   // Note: AppleScript is 1-based.
   index--;
-  [aWindow setOrderedIndex:@(index)];
+  aWindow.orderedIndex = @(index);
 }
 
 - (void)removeFromAppleScriptWindowsAtIndex:(int)index {
-  [[self appleScriptWindows][index] handlesCloseScriptCommand:nil];
+  [self.appleScriptWindows[index] handlesCloseScriptCommand:nil];
 }
 
 - (NSScriptObjectSpecifier*)objectSpecifier {
@@ -81,9 +81,9 @@
 
 - (BookmarkFolderAppleScript*)otherBookmarks {
   AppController* appDelegate =
-      base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
+      base::mac::ObjCCastStrict<AppController>(NSApp.delegate);
 
-  Profile* lastProfile = [appDelegate lastProfile];
+  Profile* lastProfile = appDelegate.lastProfile;
   if (!lastProfile) {
     AppleScript::SetError(AppleScript::Error::kGetProfile);
     return nil;
@@ -106,9 +106,9 @@
 
 - (BookmarkFolderAppleScript*)bookmarksBar {
   AppController* appDelegate =
-      base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
+      base::mac::ObjCCastStrict<AppController>(NSApp.delegate);
 
-  Profile* lastProfile = [appDelegate lastProfile];
+  Profile* lastProfile = appDelegate.lastProfile;
   if (!lastProfile) {
     AppleScript::SetError(AppleScript::Error::kGetProfile);
     return nil;
@@ -121,26 +121,23 @@
     return nullptr;
   }
 
-  BookmarkFolderAppleScript* bookmarksBar =
-      [[[BookmarkFolderAppleScript alloc]
-          initWithBookmarkNode:model->bookmark_bar_node()] autorelease];
+  BookmarkFolderAppleScript* bookmarksBar = [[[BookmarkFolderAppleScript alloc]
+      initWithBookmarkNode:model->bookmark_bar_node()] autorelease];
   [bookmarksBar setContainer:self
                     property:AppleScript::kBookmarkFoldersProperty];
   return bookmarksBar;
 }
 
-- (NSArray*)bookmarkFolders {
-  BookmarkFolderAppleScript* otherBookmarks = [self otherBookmarks];
-  BookmarkFolderAppleScript* bookmarksBar = [self bookmarksBar];
-  NSArray* folderArray = @[ otherBookmarks, bookmarksBar ];
-  return folderArray;
+- (NSArray<BookmarkFolderAppleScript*>*)bookmarkFolders {
+  return @[ self.otherBookmarks, self.bookmarksBar ];
 }
 
-- (void)insertInBookmarksFolders:(id)aBookmarkFolder {
+- (void)insertInBookmarksFolders:(BookmarkFolderAppleScript*)aBookmarkFolder {
   NOTIMPLEMENTED();
 }
 
-- (void)insertInBookmarksFolders:(id)aBookmarkFolder atIndex:(int)index {
+- (void)insertInBookmarksFolders:(BookmarkFolderAppleScript*)aBookmarkFolder
+                         atIndex:(int)index {
   NOTIMPLEMENTED();
 }
 
diff --git a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
index c43f089..f87dd89 100644
--- a/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/browsercrapplication+applescript_browsertest.mm
@@ -33,10 +33,10 @@
       "Test", /*trusted_source=*/true, gfx::Rect(), profile,
       /*user_gesture=*/true));
 
-  EXPECT_EQ(3U, [[NSApp appleScriptWindows] count]);
+  EXPECT_EQ(3U, [NSApp appleScriptWindows].count);
   for (WindowAppleScript* window in [NSApp appleScriptWindows]) {
-    EXPECT_NSEQ(AppleScript::kWindowsProperty, [window containerProperty]);
-    EXPECT_NSEQ(NSApp, [window container]);
+    EXPECT_NSEQ(AppleScript::kWindowsProperty, window.containerProperty);
+    EXPECT_NSEQ(NSApp, window.container);
   }
 
   // Close the additional browsers.
@@ -53,7 +53,7 @@
   //   set var to make new window with properties {visible:false}|.
   base::scoped_nsobject<WindowAppleScript> aWindow(
       [[WindowAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[aWindow.get() uniqueID] copy]);
+  base::scoped_nsobject<NSString> unique_id([aWindow.get().uniqueID copy]);
   [aWindow.get() setValue:@YES forKey:@"visible"];
 
   [NSApp insertInAppleScriptWindows:aWindow.get()];
@@ -62,34 +62,34 @@
   // Represents the window after it is added.
   WindowAppleScript* window = [NSApp appleScriptWindows][0];
   EXPECT_NSEQ(@YES, [aWindow.get() valueForKey:@"visible"]);
-  EXPECT_EQ([window container], NSApp);
-  EXPECT_NSEQ(AppleScript::kWindowsProperty, [window containerProperty]);
-  EXPECT_NSEQ(var, [window uniqueID]);
+  EXPECT_EQ(window.container, NSApp);
+  EXPECT_NSEQ(AppleScript::kWindowsProperty, window.containerProperty);
+  EXPECT_NSEQ(unique_id, window.uniqueID);
 }
 
 // Inserting and deleting windows.
 IN_PROC_BROWSER_TEST_F(BrowserCrApplicationAppleScriptTest,
                        InsertAndDeleteWindows) {
   base::scoped_nsobject<WindowAppleScript> aWindow;
-  int count;
+  NSUInteger count;
   // Create a bunch of windows.
-  for (int i = 0; i < 5; ++i) {
-    for (int j = 0; j < 3; ++j) {
+  for (NSUInteger i = 0; i < 5; ++i) {
+    for (NSUInteger j = 0; j < 3; ++j) {
       aWindow.reset([[WindowAppleScript alloc] init]);
       [NSApp insertInAppleScriptWindows:aWindow.get()];
     }
     count = 3 * i + 4;
-    EXPECT_EQ(count, (int)[[NSApp appleScriptWindows] count]);
+    EXPECT_EQ(count, [NSApp appleScriptWindows].count);
   }
 
   // Remove all the windows, just created.
-  count = (int)[[NSApp appleScriptWindows] count];
-  for (int i = 0; i < 5; ++i) {
-    for (int j = 0; j < 3; ++j) {
+  count = [NSApp appleScriptWindows].count;
+  for (NSUInteger i = 0; i < 5; ++i) {
+    for (NSUInteger j = 0; j < 3; ++j) {
       [NSApp removeFromAppleScriptWindowsAtIndex:0];
     }
     count = count - 3;
-    EXPECT_EQ(count, (int)[[NSApp appleScriptWindows] count]);
+    EXPECT_EQ(count, [NSApp appleScriptWindows].count);
   }
 }
 
@@ -101,19 +101,19 @@
 
 // Bookmark folders at the root level.
 IN_PROC_BROWSER_TEST_F(BrowserCrApplicationAppleScriptTest, BookmarkFolders) {
-  NSArray* bookmarkFolders = [NSApp bookmarkFolders];
-  EXPECT_EQ(2U, [bookmarkFolders count]);
+  NSArray* bookmark_folders = [NSApp bookmarkFolders];
+  EXPECT_EQ(2U, bookmark_folders.count);
 
-  for (BookmarkFolderAppleScript* bookmarkFolder in bookmarkFolders) {
-    EXPECT_EQ(NSApp, [bookmarkFolder container]);
+  for (BookmarkFolderAppleScript* bookmark_folder in bookmark_folders) {
+    EXPECT_EQ(NSApp, bookmark_folder.container);
     EXPECT_NSEQ(AppleScript::kBookmarkFoldersProperty,
-                [bookmarkFolder containerProperty]);
+                bookmark_folder.containerProperty);
   }
 
-  BookmarkFolderAppleScript* otherBookmarks =
+  BookmarkFolderAppleScript* other_bookmarks =
       base::mac::ObjCCast<BookmarkFolderAppleScript>([NSApp otherBookmarks]);
-  EXPECT_NSEQ(@"Other Bookmarks", [otherBookmarks title]);
-  BookmarkFolderAppleScript* bookmarksBar =
+  EXPECT_NSEQ(@"Other Bookmarks", other_bookmarks.title);
+  BookmarkFolderAppleScript* bookmarks_bar =
       base::mac::ObjCCast<BookmarkFolderAppleScript>([NSApp bookmarksBar]);
-  EXPECT_NSEQ(@"Bookmarks Bar", [bookmarksBar title]);
+  EXPECT_NSEQ(@"Bookmarks Bar", bookmarks_bar.title);
 }
diff --git a/chrome/browser/ui/cocoa/applescript/tab_applescript.h b/chrome/browser/ui/cocoa/applescript/tab_applescript.h
index 7f1e453..401d247 100644
--- a/chrome/browser/ui/cocoa/applescript/tab_applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/tab_applescript.h
@@ -27,17 +27,14 @@
 // Assigns a tab, sets its unique ID and also copies temporary values.
 - (void)setWebContents:(content::WebContents*)webContents;
 
-// Return the URL currently visible to the user in the location bar.
-- (NSString*)URL;
-
-// Sets the URL, returns an error if it is invalid.
-- (void)setURL:(NSString*)aURL;
+// Returns/sets the URL currently visible to the user in the location bar.
+@property(copy) NSString* URL;
 
 // The title of the tab.
-- (NSString*)title;
+@property(readonly) NSString* title;
 
 // Is the tab loading any resource?
-- (NSNumber*)loading;
+@property(readonly) NSNumber* loading;
 
 // Standard user commands.
 - (void)handlesUndoScriptCommand:(NSScriptCommand*)command;
diff --git a/chrome/browser/ui/cocoa/applescript/tab_applescript.mm b/chrome/browser/ui/cocoa/applescript/tab_applescript.mm
index ae582d2..b9a767d 100644
--- a/chrome/browser/ui/cocoa/applescript/tab_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/tab_applescript.mm
@@ -7,6 +7,7 @@
 #include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
+#include "base/mac/foundation_util.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
@@ -53,12 +54,14 @@
 
 }  // namespace
 
-@interface TabAppleScript()
+@interface TabAppleScript ()
+
 // Contains the temporary URL when a user creates a new folder/item with the URL
 // specified like:
 //
 //   make new tab with properties {URL:"http://google.com"}
 @property (nonatomic, copy) NSString* tempURL;
+
 @end
 
 @implementation TabAppleScript {
@@ -115,8 +118,8 @@
   self.uniqueID =
       [NSString stringWithFormat:@"%d", session_tab_helper->session_id().id()];
 
-  if ([self tempURL]) {
-    [self setURL:[self tempURL]];
+  if (self.tempURL) {
+    self.URL = self.tempURL;
   }
 }
 
@@ -138,7 +141,7 @@
   // it at a temporary location. Once they're set, -setURL: will be call again
   // with the temporary URL.
   if (!_profile || !_webContents) {
-    [self setTempURL:aURL];
+    self.tempURL = aURL;
     return;
   }
 
@@ -238,7 +241,7 @@
 }
 
 - (void)handlesSaveScriptCommand:(NSScriptCommand*)command {
-  NSDictionary* dictionary = [command evaluatedArguments];
+  NSDictionary* dictionary = command.evaluatedArguments;
 
   NSURL* fileURL = dictionary[@"File"];
   // Scripter has not specified the location at which to save, so we prompt for
@@ -248,7 +251,7 @@
     return;
   }
 
-  base::FilePath mainFile(base::SysNSStringToUTF8([fileURL path]));
+  base::FilePath mainFile = base::mac::NSURLToFilePath(fileURL);
   // We create a directory path at the folder within which the file exists.
   // Eg.    if main_file = '/Users/Foo/Documents/Google.html'
   // then directory_path = '/Users/Foo/Documents/Google_files/'.
diff --git a/chrome/browser/ui/cocoa/applescript/window_applescript.h b/chrome/browser/ui/cocoa/applescript/window_applescript.h
index 76a9b50..4409725 100644
--- a/chrome/browser/ui/cocoa/applescript/window_applescript.h
+++ b/chrome/browser/ui/cocoa/applescript/window_applescript.h
@@ -25,27 +25,25 @@
 // Does not create a new window but uses an existing one.
 - (instancetype)initWithBrowser:(Browser*)aBrowser;
 
-// Sets and gets the index of the currently selected tab.
-- (NSNumber*)activeTabIndex;
-- (void)setActiveTabIndex:(NSNumber*)anActiveTabIndex;
+// Sets and gets the index of the currently selected tab. 1-based because
+// this is intended for use by AppleScript.
+@property(copy) NSNumber* activeTabIndex;
 
 // Sets and get the given name of a window.
-- (NSString*)givenName;
-- (void)setGivenName:(NSString*)name;
+@property(copy) NSString* givenName;
 
 // Mode refers to whether a window is a normal window or an incognito window
 // it can be set only once while creating the window.
-- (NSString*)mode;
-- (void)setMode:(NSString*)theMode;
+@property(copy) NSString* mode;
 
 // Returns the currently selected tab.
-- (TabAppleScript*)activeTab;
+@property(readonly) TabAppleScript* activeTab;
 
 // Tab manipulation functions.
 // The tabs inside the window.
 // Returns |TabAppleScript*| of all the tabs contained
 // within this particular folder.
-- (NSArray*)tabs;
+@property(readonly) NSArray<TabAppleScript*>* tabs;
 
 // Insert a tab at the end.
 - (void)insertInTabs:(TabAppleScript*)aTab;
@@ -61,8 +59,7 @@
 - (void)removeFromTabsAtIndex:(int)index;
 
 // The index of the window, windows are ordered front to back.
-- (NSNumber*)orderedIndex;
-- (void)setOrderedIndex:(NSNumber*)anIndex;
+@property(copy) NSNumber* orderedIndex;
 
 // For standard window functions like zoomable, bounds etc, we dont handle it
 // but instead pass it onto the NSWindow associated with the window.
diff --git a/chrome/browser/ui/cocoa/applescript/window_applescript.mm b/chrome/browser/ui/cocoa/applescript/window_applescript.mm
index ad9ddf2..7976b21 100644
--- a/chrome/browser/ui/cocoa/applescript/window_applescript.mm
+++ b/chrome/browser/ui/cocoa/applescript/window_applescript.mm
@@ -34,9 +34,11 @@
 #include "chrome/common/url_constants.h"
 #include "content/public/browser/web_contents.h"
 
-@interface WindowAppleScript(WindowAppleScriptPrivateMethods)
+@interface WindowAppleScript ()
+
 // The NSWindow that corresponds to this window.
-- (NSWindow*)nativeHandle;
+@property(readonly) NSWindow* nativeHandle;
+
 @end
 
 @implementation WindowAppleScript {
@@ -46,11 +48,11 @@
 - (instancetype)init {
   // Check which mode to open a new window.
   NSScriptCommand* command = [NSScriptCommand currentCommand];
-  NSString* mode = [command evaluatedArguments][@"KeyDictionary"][@"mode"];
+  NSString* mode = command.evaluatedArguments[@"KeyDictionary"][@"mode"];
   AppController* appDelegate =
-      base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
+      base::mac::ObjCCastStrict<AppController>(NSApp.delegate);
 
-  Profile* lastProfile = [appDelegate lastProfile];
+  Profile* lastProfile = appDelegate.lastProfile;
 
   if (!lastProfile) {
     AppleScript::SetError(AppleScript::Error::kGetProfile);
@@ -60,16 +62,15 @@
   Profile* profile;
   if ([mode isEqualToString:AppleScript::kIncognitoWindowMode]) {
     profile = lastProfile->GetPrimaryOTRProfile(/*create_if_needed=*/true);
-  }
-  else if ([mode isEqualToString:AppleScript::kNormalWindowMode] || !mode) {
+  } else if ([mode isEqualToString:AppleScript::kNormalWindowMode] || !mode) {
     profile = lastProfile;
   } else {
-    // Mode cannot be anything else
+    // Mode cannot be anything else.
     AppleScript::SetError(AppleScript::Error::kInvalidMode);
     return nil;
   }
   // Set the mode to nil, to ensure that it is not set once more.
-  [[command evaluatedArguments][@"KeyDictionary"] setValue:nil forKey:@"mode"];
+  [command.evaluatedArguments[@"KeyDictionary"] setValue:nil forKey:@"mode"];
   return [self initWithProfile:profile];
 }
 
@@ -160,7 +161,7 @@
 }
 
 - (void)setMode:(NSString*)theMode {
-  // cannot set mode after window is created.
+  // Cannot set mode after window is created.
   if (theMode) {
     AppleScript::SetError(AppleScript::Error::kSetMode);
   }
@@ -175,7 +176,7 @@
   return currentTab;
 }
 
-- (NSArray*)tabs {
+- (NSArray<TabAppleScript*>*)tabs {
   TabStripModel* tabStrip = _browser->tab_strip_model();
   NSMutableArray* tabs = [NSMutableArray arrayWithCapacity:tabStrip->count()];
 
@@ -241,29 +242,29 @@
 }
 
 - (NSNumber*)orderedIndex {
-  return @([[self nativeHandle] orderedIndex]);
+  return @(self.nativeHandle.orderedIndex);
 }
 
 - (void)setOrderedIndex:(NSNumber*)anIndex {
-  int index = [anIndex intValue] - 1;
+  int index = anIndex.intValue - 1;
   if (index < 0 || index >= static_cast<int>(chrome::GetTotalBrowserCount())) {
     AppleScript::SetError(AppleScript::Error::kWrongIndex);
     return;
   }
-  [[self nativeHandle] setOrderedIndex:index];
+  self.nativeHandle.orderedIndex = index;
 }
 
 // Get and set values from the associated NSWindow.
 - (id)valueForUndefinedKey:(NSString*)key {
-  return [[self nativeHandle] valueForKey:key];
+  return [self.nativeHandle valueForKey:key];
 }
 
 - (void)setValue:(id)value forUndefinedKey:(NSString*)key {
-  [[self nativeHandle] setValue:value forKey:key];
+  [self.nativeHandle setValue:value forKey:key];
 }
 
 - (void)handlesCloseScriptCommand:(NSCloseCommand*)command {
-  // window() can be NULL during startup.
+  // window() can be null during startup.
   if (_browser->window()) {
     _browser->window()->Close();
   }
diff --git a/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm b/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
index 57f5042..0a152b5 100644
--- a/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
+++ b/chrome/browser/ui/cocoa/applescript/window_applescript_browsertest.mm
@@ -27,8 +27,8 @@
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, DefaultCreation) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] init]);
-  EXPECT_TRUE(window.get());
-  NSString* mode = [window.get() mode];
+  EXPECT_TRUE(window);
+  NSString* mode = window.get().mode;
   EXPECT_NSEQ(AppleScript::kNormalWindowMode, mode);
 }
 
@@ -36,44 +36,44 @@
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, CreationWithNoProfile) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithProfile:nullptr]);
-  EXPECT_FALSE(window.get());
+  EXPECT_FALSE(window);
 }
 
 // Create a window with a particular profile.
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, CreationWithProfile) {
   AppController* appController =
-      base::mac::ObjCCastStrict<AppController>([NSApp delegate]);
-  Profile* lastProfile = [appController lastProfile];
+      base::mac::ObjCCastStrict<AppController>(NSApp.delegate);
+  Profile* lastProfile = appController.lastProfile;
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithProfile:lastProfile]);
-  EXPECT_TRUE(window.get());
-  EXPECT_TRUE([window.get() uniqueID]);
+  EXPECT_TRUE(window);
+  EXPECT_TRUE(window.get().uniqueID);
 }
 
 // Create a window with no |Browser*|.
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, CreationWithNoBrowser) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:nullptr]);
-  EXPECT_FALSE(window.get());
+  EXPECT_FALSE(window);
 }
 
 // Create a window with |Browser*| already present.
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, CreationWithBrowser) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
-  EXPECT_TRUE(window.get());
-  EXPECT_TRUE([window.get() uniqueID]);
+  EXPECT_TRUE(window);
+  EXPECT_TRUE(window.get().uniqueID);
 }
 
 // Tabs within the window.
 IN_PROC_BROWSER_TEST_F(WindowAppleScriptTest, Tabs) {
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
-  NSArray* tabs = [window.get() tabs];
-  EXPECT_EQ(1U, [tabs count]);
+  NSArray* tabs = window.get().tabs;
+  EXPECT_EQ(1U, tabs.count);
   TabAppleScript* tab1 = tabs[0];
-  EXPECT_EQ([tab1 container], window.get());
-  EXPECT_NSEQ(AppleScript::kTabsProperty, [tab1 containerProperty]);
+  EXPECT_EQ(tab1.container, window.get());
+  EXPECT_NSEQ(AppleScript::kTabsProperty, tab1.containerProperty);
 }
 
 // Insert a new tab.
@@ -83,19 +83,18 @@
   //
   //   set var to make new tab with properties {URL:"http://google.com"}
   base::scoped_nsobject<TabAppleScript> aTab([[TabAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[aTab.get() uniqueID] copy]);
-  [aTab.get() setURL:@"http://google.com"];
+  base::scoped_nsobject<NSString> unique_id([aTab.get().uniqueID copy]);
+  aTab.get().URL = @"http://google.com";
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
   [window.get() insertInTabs:aTab.get()];
 
   // Represents the tab after it is inserted.
-  TabAppleScript* tab = [window.get() tabs][1];
-  EXPECT_EQ(GURL("http://google.com"),
-            GURL(base::SysNSStringToUTF8([tab URL])));
-  EXPECT_EQ([tab container], window.get());
-  EXPECT_NSEQ(AppleScript::kTabsProperty, [tab containerProperty]);
-  EXPECT_NSEQ(var.get(), [tab uniqueID]);
+  TabAppleScript* tab = window.get().tabs[1];
+  EXPECT_EQ(GURL("http://google.com"), GURL(base::SysNSStringToUTF8(tab.URL)));
+  EXPECT_EQ(tab.container, window.get());
+  EXPECT_NSEQ(AppleScript::kTabsProperty, tab.containerProperty);
+  EXPECT_NSEQ(unique_id, tab.uniqueID);
 }
 
 // Insert a new tab at a particular position
@@ -106,19 +105,18 @@
   //   set var to make new tab with properties
   //       {URL:"http://google.com"} at before tab 1
   base::scoped_nsobject<TabAppleScript> aTab([[TabAppleScript alloc] init]);
-  base::scoped_nsobject<NSString> var([[aTab.get() uniqueID] copy]);
-  [aTab.get() setURL:@"http://google.com"];
+  base::scoped_nsobject<NSString> unique_id([aTab.get().uniqueID copy]);
+  aTab.get().URL = @"http://google.com";
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
   [window.get() insertInTabs:aTab.get() atIndex:0];
 
   // Represents the tab after it is inserted.
-  TabAppleScript* tab = [window.get() tabs][0];
-  EXPECT_EQ(GURL("http://google.com"),
-            GURL(base::SysNSStringToUTF8([tab URL])));
-  EXPECT_EQ([tab container], window.get());
-  EXPECT_NSEQ(AppleScript::kTabsProperty, [tab containerProperty]);
-  EXPECT_NSEQ(var.get(), [tab uniqueID]);
+  TabAppleScript* tab = window.get().tabs[0];
+  EXPECT_EQ(GURL("http://google.com"), GURL(base::SysNSStringToUTF8(tab.URL)));
+  EXPECT_EQ(tab.container, window.get());
+  EXPECT_NSEQ(AppleScript::kTabsProperty, tab.containerProperty);
+  EXPECT_NSEQ(unique_id, tab.uniqueID);
 }
 
 // Inserting and deleting tabs.
@@ -126,23 +124,23 @@
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
   base::scoped_nsobject<TabAppleScript> aTab;
-  int count;
-  for (int i = 0; i < 5; ++i) {
-    for (int j = 0; j < 3; ++j) {
+  NSUInteger count;
+  for (NSUInteger i = 0; i < 5; ++i) {
+    for (NSUInteger j = 0; j < 3; ++j) {
       aTab.reset([[TabAppleScript alloc] init]);
       [window.get() insertInTabs:aTab.get()];
     }
     count = 3 * i + 4;
-    EXPECT_EQ((int)[[window.get() tabs] count], count);
+    EXPECT_EQ(window.get().tabs.count, count);
   }
 
-  count = (int)[[window.get() tabs] count];
-  for (int i = 0; i < 5; ++i) {
-    for (int j = 0; j < 3; ++j) {
+  count = window.get().tabs.count;
+  for (NSUInteger i = 0; i < 5; ++i) {
+    for (NSUInteger j = 0; j < 3; ++j) {
       [window.get() removeFromTabsAtIndex:0];
     }
     count = count - 3;
-    EXPECT_EQ((int)[[window.get() tabs] count], count);
+    EXPECT_EQ(window.get().tabs.count, count);
   }
 }
 
@@ -161,9 +159,9 @@
   base::scoped_nsobject<WindowAppleScript> window(
       [[WindowAppleScript alloc] initWithBrowser:browser()]);
   base::scoped_nsobject<TabAppleScript> aTab([[TabAppleScript alloc] init]);
-  [window.get() insertInTabs:aTab.get()];
-  [window.get() setActiveTabIndex:@2];
-  EXPECT_EQ(2, [[window.get() activeTabIndex] intValue]);
-  TabAppleScript* tab2 = [window.get() tabs][1];
-  EXPECT_NSEQ([[window.get() activeTab] uniqueID], [tab2 uniqueID]);
+  [window insertInTabs:aTab];
+  [window setActiveTabIndex:@2];
+  EXPECT_EQ(2, window.get().activeTabIndex.intValue);
+  TabAppleScript* tab2 = window.get().tabs[1];
+  EXPECT_NSEQ(window.get().activeTab.uniqueID, tab2.uniqueID);
 }
diff --git a/chrome/browser/ui/color/BUILD.gn b/chrome/browser/ui/color/BUILD.gn
index 1a5dd954..e27675d 100644
--- a/chrome/browser/ui/color/BUILD.gn
+++ b/chrome/browser/ui/color/BUILD.gn
@@ -47,6 +47,7 @@
     "//build:chromeos_buildflags",
     "//chrome/browser:theme_properties",
     "//chrome/common/themes:autogenerated_theme_util",
+    "//components/omnibox/common:common",
     "//components/search:search",
     "//ui/base:buildflags",
     "//ui/color:color",
diff --git a/chrome/browser/ui/color/chrome_color_mixer.cc b/chrome/browser/ui/color/chrome_color_mixer.cc
index d217fa7a..5bc0003c7 100644
--- a/chrome/browser/ui/color/chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/chrome_color_mixer.cc
@@ -4,11 +4,17 @@
 
 #include "chrome/browser/ui/color/chrome_color_mixer.h"
 
+#include <string>
+
+#include "base/feature_list.h"
+#include "base/strings/string_number_conversions.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/color/chrome_color_provider_utils.h"
+#include "components/omnibox/common/omnibox_features.h"
 #include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/ui_base_features.h"
 #include "ui/color/color_id.h"
 #include "ui/color/color_mixer.h"
 #include "ui/color/color_provider.h"
@@ -79,6 +85,77 @@
 // Alpha of 61 = 24% opacity. Opacity of tab group chips in the bookmarks bar.
 constexpr SkAlpha kTabGroupChipAlpha = 61;
 
+// Apply updates to the Omnibox background color tokens per GM3 spec.
+void ApplyGM3OmniboxBackgroundColor(ui::ColorMixer& mixer,
+                                    const ui::ColorProviderManager::Key& key) {
+  const bool gm3_background_color_enabled =
+      features::IsChromeRefresh2023() ||
+      base::FeatureList::IsEnabled(omnibox::kOmniboxSteadyStateBackgroundColor);
+
+  // Apply omnibox background color updates only to non-themed clients.
+  if (gm3_background_color_enabled && !key.custom_theme) {
+    // Retrieve GM3 omnibox background color params (Dark Mode).
+    const std::string dark_background_color_param =
+        omnibox::kOmniboxDarkBackgroundColor.Get();
+    const std::string dark_background_color_hovered_param =
+        omnibox::kOmniboxDarkBackgroundColorHovered.Get();
+
+    // Retrieve GM3 omnibox background color params (Light Mode).
+    const std::string light_background_color_param =
+        omnibox::kOmniboxLightBackgroundColor.Get();
+    const std::string light_background_color_hovered_param =
+        omnibox::kOmniboxLightBackgroundColorHovered.Get();
+
+    const auto string_to_skcolor = [](const std::string& rgb_str,
+                                      SkColor* result) {
+      // Valid color strings are of the form 0xRRGGBB or 0xAARRGGBB.
+      const bool valid =
+          result && (rgb_str.size() == 8 || rgb_str.size() == 10);
+      if (!valid) {
+        return false;
+      }
+
+      uint32_t parsed = 0;
+      const bool success = base::HexStringToUInt(rgb_str, &parsed);
+      if (success) {
+        *result = SkColorSetA(static_cast<SkColor>(parsed), SK_AlphaOPAQUE);
+      }
+      return success;
+    };
+
+    SkColor dark_background_color = 0;
+    SkColor dark_background_color_hovered = 0;
+
+    SkColor light_background_color = 0;
+    SkColor light_background_color_hovered = 0;
+
+    const bool success = string_to_skcolor(dark_background_color_param,
+                                           &dark_background_color) &&
+                         string_to_skcolor(dark_background_color_hovered_param,
+                                           &dark_background_color_hovered) &&
+                         string_to_skcolor(light_background_color_param,
+                                           &light_background_color) &&
+                         string_to_skcolor(light_background_color_hovered_param,
+                                           &light_background_color_hovered);
+
+    if (!success) {
+      return;
+    }
+
+    const auto selected_background_color = ui::SelectBasedOnDarkInput(
+        kColorToolbar, dark_background_color, light_background_color);
+
+    mixer[kColorLocationBarBackground] = {selected_background_color};
+
+    const auto selected_background_color_hovered =
+        ui::SelectBasedOnDarkInput(kColorToolbar, dark_background_color_hovered,
+                                   light_background_color_hovered);
+
+    mixer[kColorLocationBarBackgroundHovered] = {
+        selected_background_color_hovered};
+  }
+}
+
 }  // namespace
 
 void AddChromeColorMixer(ui::ColorProvider* provider,
@@ -229,9 +306,16 @@
   mixer[kColorIntentPickerItemBackgroundSelected] = ui::BlendForMinContrast(
       ui::kColorDialogBackground, ui::kColorDialogBackground,
       ui::kColorAccentWithGuaranteedContrastAtopPrimaryBackground, 1.2);
+
+  // By default, the Omnibox background color will be determined by the toolbar
+  // color.
   mixer[kColorLocationBarBackground] = {kColorToolbarBackgroundSubtleEmphasis};
   mixer[kColorLocationBarBackgroundHovered] = {
       kColorToolbarBackgroundSubtleEmphasisHovered};
+
+  // Override Omnibox background color tokens per GM3 spec when appropriate.
+  ApplyGM3OmniboxBackgroundColor(mixer, key);
+
   mixer[kColorLocationBarBorder] = {SkColorSetA(SK_ColorBLACK, 0x4D)};
   mixer[kColorLocationBarBorderOpaque] =
       ui::GetResultingPaintColor(kColorLocationBarBorder, kColorToolbar);
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
index 1b9c3d0..7535f65 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
@@ -58,7 +58,7 @@
 // This value comes from tab_group_style.cc (kEmptyChipSize). Since this
 // button and the tab_group_header are rendered on different surfaces, keep
 // the value here in case we want to change one but not the other.
-constexpr float kCircleRadius = 20.0f;
+constexpr float kCircleRadius = 14.0f;
 }  // namespace
 
 SavedTabGroupButton::SavedTabGroupButton(
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_view_views.cc
index 32ec94c..7a48c12 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_popup_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_popup_view_views.cc
@@ -504,9 +504,8 @@
     case ui::ET_GESTURE_TAP:
     case ui::ET_GESTURE_SCROLL_END: {
       DCHECK(HasMatchAt(index));
-      const AutocompleteMatch& match = edit_model_->result().match_at(index);
-      edit_model_->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
-                             u"", index, event->time_stamp());
+      edit_model_->OpenSelection(OmniboxPopupSelection(index),
+                                 event->time_stamp());
       break;
     }
     default:
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index d13f316..143b7c0 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -466,8 +466,8 @@
         event.IsOnlyLeftMouseButton()
             ? WindowOpenDisposition::CURRENT_TAB
             : WindowOpenDisposition::NEW_BACKGROUND_TAB;
-    model_->OpenMatch(match_, disposition, GURL(), u"", model_index_,
-                      event.time_stamp());
+    model_->OpenSelection(OmniboxPopupSelection(model_index_),
+                          event.time_stamp(), disposition);
   }
 }
 
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index 95231f5..f195885 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -1599,8 +1599,19 @@
       } else if (shift) {
         disposition = WindowOpenDisposition::NEW_WINDOW;
       }
-      model()->OpenSelection(model()->GetPopupSelection(), event.time_stamp(),
-                             disposition);
+      // According to unit tests and comments, holding control when pressing
+      // enter has special behavior handled by `AcceptInput` so in this case
+      // the user is selecting their input (possibly with modification like
+      // appending ".com") and not the row match. This is indicated with an
+      // explicit `kNoMatch` line selection.
+      if (model()->PopupIsOpen() && !control) {
+        model()->OpenSelection(model()->GetPopupSelection(), event.time_stamp(),
+                               disposition);
+      } else {
+        model()->OpenSelection(
+            OmniboxPopupSelection(OmniboxPopupSelection::kNoMatch),
+            event.time_stamp(), disposition);
+      }
       return true;
     }
     case ui::VKEY_ESCAPE:
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
index 2397446..ce3bd060 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.cc
@@ -57,6 +57,10 @@
       std::make_unique<SidePanelWebUIViewT<CompanionSidePanelUntrustedUI>>(
           base::RepeatingClosure(), base::RepeatingClosure(),
           std::move(wrapper));
+
+  // Observe on the webcontents for opening links in new tab.
+  Observe(companion_web_view->GetWebContents());
+
   // Need to set browser after SidePanelWebUIViewT is constructed since it
   // creates the WebUIController. The WebUI needs a Browser pointer in order
   // to observe changes to the tab strip model.
@@ -108,4 +112,38 @@
           base::Unretained(this)));
 }
 
+// This method is called when the WebContents wants to open a link in a new
+// tab. This delegate does not override AddNewContents(), so the webcontents
+// is not actually created. Instead it forwards the parameters to the real
+// browser.
+void SearchCompanionSidePanelCoordinator::DidOpenRequestedURL(
+    content::WebContents* new_contents,
+    content::RenderFrameHost* source_render_frame_host,
+    const GURL& url,
+    const content::Referrer& referrer,
+    WindowOpenDisposition disposition,
+    ui::PageTransition transition,
+    bool started_from_context_menu,
+    bool renderer_initiated) {
+  content::OpenURLParams params(url, referrer, disposition, transition,
+                                renderer_initiated);
+
+  // If the navigation is initiated by the renderer process, we must set an
+  // initiator origin.
+  if (renderer_initiated) {
+    params.initiator_origin = url::Origin::Create(url);
+  }
+
+  // Open the new tab in the foreground.
+  params.disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+
+  auto* browser_view = GetBrowserView();
+  if (!browser_view) {
+    return;
+  }
+
+  // Open the url in a new tab.
+  browser_view->browser()->OpenURL(params);
+}
+
 WEB_CONTENTS_USER_DATA_KEY_IMPL(SearchCompanionSidePanelCoordinator);
diff --git a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
index 9b8454df..0e7be625 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/search_companion/search_companion_side_panel_coordinator.h
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
+#include "content/public/browser/web_contents_observer.h"
 
 class Browser;
 class Profile;
@@ -24,6 +25,7 @@
 // the search companion SidePanelEntry.
 class SearchCompanionSidePanelCoordinator
     : public BrowserUserData<SearchCompanionSidePanelCoordinator>,
+      public content::WebContentsObserver,
       public TabStripModelObserver {
  public:
   explicit SearchCompanionSidePanelCoordinator(Browser* browser);
@@ -61,6 +63,16 @@
 
   std::unique_ptr<views::View> CreateCompanionWebView();
 
+  // content::WebContentsObserver:
+  void DidOpenRequestedURL(content::WebContents* new_contents,
+                           content::RenderFrameHost* source_render_frame_host,
+                           const GURL& url,
+                           const content::Referrer& referrer,
+                           WindowOpenDisposition disposition,
+                           ui::PageTransition transition,
+                           bool started_from_context_menu,
+                           bool renderer_initiated) override;
+
   BROWSER_USER_DATA_KEY_DECL();
 };
 
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
index f0c61639..d95e624 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.cc
@@ -48,6 +48,7 @@
 PWAConfirmationBubbleView* g_bubble_ = nullptr;
 
 bool g_auto_accept_pwa_for_testing = false;
+bool g_dont_close_on_deactivate = false;
 
 // Returns an ImageView containing the app icon.
 std::unique_ptr<views::ImageView> CreateIconView(
@@ -147,6 +148,10 @@
   }
 
   SetHighlightedButton(highlight_icon_button_);
+
+  if (g_dont_close_on_deactivate) {
+    set_close_on_deactivate(false);
+  }
 }
 
 PWAConfirmationBubbleView::~PWAConfirmationBubbleView() = default;
@@ -205,6 +210,11 @@
   return true;
 }
 
+base::AutoReset<bool>
+PWAConfirmationBubbleView::SetDontCloseOnDeactivateForTesting() {
+  return base::AutoReset<bool>(&g_dont_close_on_deactivate, true);
+}
+
 void PWAConfirmationBubbleView::OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
                                 views::Widget* widget) const {
   params->name = "PWAConfirmationBubbleView";
diff --git a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
index 8694b7a..d054bf4 100644
--- a/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
+++ b/chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h
@@ -51,6 +51,8 @@
   void WindowClosing() override;
   bool Accept() override;
 
+  static base::AutoReset<bool> SetDontCloseOnDeactivateForTesting();
+
  protected:
   void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
                                 views::Widget* widget) const override;
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index 876ad9d0..f275232 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -63,6 +63,7 @@
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/web_app_toolbar_button_container.h"
 #include "chrome/browser/ui/views/web_apps/frame_toolbar/window_controls_overlay_toggle_button.h"
+#include "chrome/browser/ui/views/web_apps/pwa_confirmation_bubble_view.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_utils.h"
@@ -1115,6 +1116,8 @@
   BrowserAddedWaiter browser_added_waiter;
   WebAppTestInstallWithOsHooksObserver install_observer(profile());
   install_observer.BeginListening();
+  auto dont_close_bubble_on_deactivate =
+      PWAConfirmationBubbleView::SetDontCloseOnDeactivateForTesting();
 
   CHECK(chrome::ExecuteCommand(browser(), IDC_INSTALL_PWA));
 
@@ -1128,7 +1131,6 @@
   ASSERT_TRUE(app_browser_);
   active_app_id_ = install_observer.Wait();
   ActivateBrowserAndWait(app_browser_);
-  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(/*auto_accept=*/false);
   AppReadinessWaiter(profile(), active_app_id_).Await();
   AfterStateChangeAction();
 }
@@ -1177,6 +1179,9 @@
         run_loop.Quit();
       }));
 
+  auto dont_close_bubble_on_deactivate =
+      PWAConfirmationBubbleView::SetDontCloseOnDeactivateForTesting();
+
   BrowserAddedWaiter browser_added_waiter;
   ASSERT_TRUE(pwa_install_view()->GetVisible());
   WebAppTestInstallWithOsHooksObserver install_observer(profile());
@@ -1192,7 +1197,6 @@
   active_app_id_ = install_observer.Wait();
   DCHECK_EQ(app_id, active_app_id_);
   ActivateBrowserAndWait(app_browser_);
-  chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
   AppReadinessWaiter(profile(), active_app_id_).Await();
   AfterStateChangeAction();
 }
diff --git a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
index c0b538a..5464cee3 100644
--- a/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/accessibility_section.cc
@@ -598,6 +598,9 @@
       {"chromeVoxTtsSettingsLink", IDS_SETTINGS_CHROMEVOX_TTS_SETTINGS_LINK},
       {"chromeVoxTtsSettingsDescription",
        IDS_SETTINGS_CHROMEVOX_TTS_SETTINGS_DESCRIPTION},
+      {"chromeVoxBrailleWordWrap", IDS_SETTINGS_CHROMEVOX_BRAILLE_WORD_WRAP},
+      {"chromeVoxMenuBrailleCommands",
+       IDS_SETTINGS_CHROMEVOX_MENU_BRAILLE_COMMANDS},
       {"chromeVoxEventLogLink", IDS_SETTINGS_CHROMEVOX_EVENT_LOG_LINK},
       {"chromeVoxEventLogDescription",
        IDS_SETTINGS_CHROMEVOX_EVENT_LOG_DESCRIPTION},
diff --git a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
index 532c5e9..7eb0afbd 100644
--- a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
+++ b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
@@ -251,7 +251,7 @@
   }
 
   if (network_time.is_null() || build_date_.is_null() ||
-      build_date_ > network_time) {
+      (!simulating_outdated_ && build_date_ > network_time)) {
     // TODO(crbug.com/1407664): Figure out why this is failing and either fix it
     // and turn the conditional above into a CHECK or document how this can fail
     // in the wild and either keep the DumpWithoutCrashing() or remove it.
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index 852eb7a..9f20451 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -1187,7 +1187,7 @@
                  kHasRecognizedCredential);
     mechanisms_.emplace_back(
         Mechanism::WindowsAPI(), desc, desc,
-        GetTransportIcon(AuthenticatorTransport::kUsbHumanInterfaceDevice),
+        GetTransportIcon(AuthenticatorTransport::kInternal),
         base::BindRepeating(&AuthenticatorRequestDialogModel::StartWinNativeApi,
                             base::Unretained(this), mechanisms_.size()),
         !priority_transport.has_value() && win_api_should_be_priority);
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 4c1e6c4..48c3a8f 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1679507993-53772962ffb7f7587e0b7eae4acc0e20545d39a1.profdata
+chrome-mac-arm-main-1679522254-405b348552e4437f9b30c54ef54e6d97dcb10007.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b47e9a8..68ad4b8 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1679507993-848d6670ef5c4e4105bd1796882f08193c258730.profdata
+chrome-win64-main-1679518779-7df988fb2b0e7e3183f0dc1a878ba771e25bb783.profdata
diff --git a/chrome/test/data/extensions/api_test/content_scripts/prerendering/test.js b/chrome/test/data/extensions/api_test/content_scripts/prerendering/test.js
index bcddce2..4b102efa 100644
--- a/chrome/test/data/extensions/api_test/content_scripts/prerendering/test.js
+++ b/chrome/test/data/extensions/api_test/content_scripts/prerendering/test.js
@@ -136,7 +136,8 @@
   // `match_origin_as_fallback` and manifest v3.
   chrome.test.runTests([
     testWithIframe,
-    testWithAboutBlankIframe,
-    testWithAboutBlankIframe.bind(this, {matchAboutBlank: true}),
+    // TODO(crbug.com/1344548): These two tests are flaky and time out.
+    // testWithAboutBlankIframe,
+    // testWithAboutBlankIframe.bind(this, {matchAboutBlank: true}),
   ]);
 });
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
index cb7e697..89d416f 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
@@ -6,6 +6,7 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
 import {fetchGooglePhotosAlbums, getCountText, GooglePhotosAlbum, GooglePhotosAlbums, initializeGooglePhotosData, PersonalizationActionName, PersonalizationRouter, SetErrorAction, WallpaperGridItem} from 'chrome://personalization/js/personalization_app.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {assertDeepEquals, assertEquals, assertGT, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {TestMock} from 'chrome://webui-test/test_mock.js';
@@ -29,6 +30,17 @@
     return matches ? [...matches] : null;
   }
 
+  function getAlbumAriaLabel(album: GooglePhotosAlbum|undefined): string {
+    if (!album) {
+      return '';
+    }
+    const primaryText = album.title;
+    const secondaryText = album.isShared ?
+        loadTimeData.getString('googlePhotosAlbumShared') :
+        getCountText(album.photoCount);
+    return `${primaryText} ${secondaryText}`;
+  }
+
   /** Scrolls the window to the bottom. */
   async function scrollToBottom() {
     window.scroll({top: document.body.scrollHeight, behavior: 'smooth'});
@@ -243,7 +255,8 @@
 
     // Albums should be aria-labeled.
     albumEls!.forEach((albumEl, i) => {
-      assertEquals(albumEl.getAttribute('aria-label'), albums[i]!.title);
+      assertEquals(
+          albumEl.getAttribute('aria-label'), getAlbumAriaLabel(albums[i]));
       assertEquals(albumEl.getAttribute('aria-posinset'), (i + 1).toString());
     });
 
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
index 6eeac31..42b7febd 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/BUILD.gn
@@ -38,6 +38,7 @@
   ]
   deps = [
     "../..:build_ts",
+    "//ash/webui/common/resources:build_ts",
     "//ash/webui/shortcut_customization_ui/resources:build_ts",
     "//ui/webui/resources/cr_elements:build_ts",
     "//ui/webui/resources/js:build_ts",
diff --git a/chrome/test/data/webui/chromeos/shortcut_customization/search_box_test.ts b/chrome/test/data/webui/chromeos/shortcut_customization/search_box_test.ts
index 9652fb24..a7567bd 100644
--- a/chrome/test/data/webui/chromeos/shortcut_customization/search_box_test.ts
+++ b/chrome/test/data/webui/chromeos/shortcut_customization/search_box_test.ts
@@ -4,6 +4,9 @@
 import 'chrome://shortcut-customization/js/search/search_box.js';
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
+import {IronDropdownElement} from '//resources/polymer/v3_0/iron-dropdown/iron-dropdown.js';
+import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
+import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {fakeSearchResults} from 'chrome://shortcut-customization/js/fake_data.js';
 import {FakeShortcutSearchHandler} from 'chrome://shortcut-customization/js/search/fake_shortcut_search_handler.js';
@@ -47,13 +50,16 @@
     searchBoxElement = initSearchBoxElement();
     await flush();
 
-    const searchFieldElement =
-        searchBoxElement!.shadowRoot!.querySelector('#search');
-    assertTrue(!!searchFieldElement);
+    const searchFieldElement = strictQuery(
+        '#search', searchBoxElement.shadowRoot, CrToolbarSearchFieldElement);
+    const dropdownElement = strictQuery(
+                                'iron-dropdown', searchBoxElement.shadowRoot,
+                                HTMLElement) as IronDropdownElement;
 
     // Before: No search results shown.
     assertEquals('', searchFieldElement.textContent);
     assertFalse(searchBoxElement.shouldShowDropdown);
+    assertFalse(dropdownElement.opened);
     assertEquals(0, searchBoxElement.searchResults.length);
 
     // Press enter will invoke shortcut search.
@@ -66,6 +72,12 @@
     // After: Fake search results shown.
     assertEquals('query', searchFieldElement.textContent);
     assertTrue(searchBoxElement.shouldShowDropdown);
+    assertTrue(dropdownElement.opened);
     assertEquals(3, searchBoxElement.searchResults.length);
+
+    // Click outside and the dropdown should be closed.
+    searchBoxElement.dispatchEvent(new Event('blur'));
+    assertFalse(searchBoxElement.shouldShowDropdown);
+    assertFalse(dropdownElement.opened);
   });
 });
diff --git a/chrome/test/data/webui/nearby_share/BUILD.gn b/chrome/test/data/webui/nearby_share/BUILD.gn
index 4fefa63f..3c5b48e 100644
--- a/chrome/test/data/webui/nearby_share/BUILD.gn
+++ b/chrome/test/data/webui/nearby_share/BUILD.gn
@@ -16,8 +16,21 @@
   # Add local test files.
   input_files_base_dir = rebase_path(".", "//")
   input_files = [
-    "shared/fake_nearby_share_settings.js",
+    "fake_mojo_interfaces.js",
+    "nearby_confirmation_page_test.js",
+    "nearby_discovery_page_test.js",
+    "nearby_share_app_test.js",
     "shared/fake_nearby_contact_manager.js",
+    "shared/fake_nearby_share_settings.js",
+    "shared/nearby_contact_visibility_test.js",
+    "shared/nearby_device_icon_test.js",
+    "shared/nearby_device_test.js",
+    "shared/nearby_onboarding_one_page_test.js",
+    "shared/nearby_onboarding_page_test.js",
+    "shared/nearby_page_template_test.js",
+    "shared/nearby_preview_test.js",
+    "shared/nearby_progress_test.js",
+    "shared/nearby_visibility_page_test.js",
   ]
 }
 
diff --git a/chrome/test/data/webui/nearby_share/nearby_browsertest.js b/chrome/test/data/webui/nearby_share/nearby_browsertest.js
index df1ff6b..212f6b3 100644
--- a/chrome/test/data/webui/nearby_share/nearby_browsertest.js
+++ b/chrome/test/data/webui/nearby_share/nearby_browsertest.js
@@ -38,8 +38,7 @@
   this[className] = class extends NearbyBrowserTest {
     /** @override */
     get browsePreload() {
-      return `chrome://nearby/test_loader.html?module=nearby_share/${
-          module}&host=test`;
+      return `chrome://nearby/test_loader.html?module=nearby_share/${module}`;
     }
   };
 
diff --git a/chrome/test/data/webui/nearby_share/shared/nearby_shared_v3_browsertest.js b/chrome/test/data/webui/nearby_share/shared/nearby_shared_v3_browsertest.js
index ef3167f..4688398 100644
--- a/chrome/test/data/webui/nearby_share/shared/nearby_shared_v3_browsertest.js
+++ b/chrome/test/data/webui/nearby_share/shared/nearby_shared_v3_browsertest.js
@@ -44,7 +44,7 @@
     /** @override */
     get browsePreload() {
       return `chrome://nearby/test_loader.html?module=nearby_share/shared/${
-          module}&host=test`;
+          module}`;
     }
   };
 
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index e021a12fb..35b831ac 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -109,7 +109,6 @@
     "os_paired_bluetooth_list_item_tests.js",
     "os_paired_bluetooth_list_tests.js",
     "os_people_page_test.js",
-    "os_printing_page_tests.js",
     "os_privacy_page_test.js",
     "os_reset_page_test.js",
     "os_saved_devices_list_tests.js",
@@ -126,7 +125,6 @@
     "per_device_keyboard_test.js",
     "per_device_mouse_subsection_test.js",
     "per_device_pointing_stick_subsection_test.js",
-    "per_device_pointing_stick_test.js",
     "per_device_touchpad_subsection_test.js",
     "per_device_touchpad_test.js",
     "personalization_page_with_personalization_hub_test.js",
@@ -209,6 +207,7 @@
     "date_time_page/timezone_subpage_test.ts",
 
     "device_page/per_device_mouse_test.ts",
+    "device_page/per_device_pointing_stick_test.ts",
 
     "internet_page/tether_connection_dialog_test.ts",
 
@@ -232,6 +231,8 @@
 
     "os_people_page/add_user_dialog_tests.ts",
 
+    "os_printing_page/os_printing_page_tests.ts",
+
     "os_settings_ui/os_settings_ui_about_page_test.js",
     "os_settings_ui/os_settings_ui_menu_test.js",
     "os_settings_ui/os_settings_ui_test.ts",
@@ -256,11 +257,16 @@
     "//ui/webui/resources/mojo:build_ts",
   ]
 
-  ts_path_mappings =
-      [ "chrome://os-settings/*|" +
-        rebase_path(
-            "$root_gen_dir/chrome/browser/resources/settings/chromeos/tsc/*",
-            target_gen_dir) ]
+  ts_path_mappings = [
+    # CrOS Settings browser tests should only import from one of the paths
+    # below, so that tests work both in optimize_webui=true/false modes.
+    "chrome://os-settings/chromeos/os_settings.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/settings/chromeos/tsc/chromeos/os_settings.d.ts",
+            target_gen_dir),
+    "chrome://os-settings/chromeos/lazy_load.js|" + rebase_path(
+            "$root_gen_dir/chrome/browser/resources/settings/chromeos/tsc/chromeos/lazy_load.d.ts",
+            target_gen_dir),
+  ]
 
   ts_definitions = ts_definition_files
 }
diff --git a/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
index 27aa027..fb4bc039 100644
--- a/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/chromevox_subpage_tests.js
@@ -73,6 +73,20 @@
       type: ControlType.TOGGLE,
     },
     {
+      id: 'brailleWordWrapToggle',
+      prefKey: 'settings.a11y.chromevox.braille_word_wrap',
+      defaultValue: true,
+      secondaryValue: false,
+      type: ControlType.TOGGLE,
+    },
+    {
+      id: 'menuBrailleCommandsToggle',
+      prefKey: 'settings.a11y.chromevox.menu_braille_commands',
+      defaultValue: false,
+      secondaryValue: true,
+      type: ControlType.TOGGLE,
+    },
+    {
       id: 'enableEarconLoggingToggle',
       prefKey: 'settings.a11y.chromevox.enable_earcon_logging',
       defaultValue: false,
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/per_device_pointing_stick_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/per_device_pointing_stick_test.ts
new file mode 100644
index 0000000..be71be7
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/device_page/per_device_pointing_stick_test.ts
@@ -0,0 +1,55 @@
+// 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 {fakePointingSticks, fakePointingSticks2, SettingsPerDevicePointingStickElement} from 'chrome://os-settings/chromeos/os_settings.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+suite('<settings-per-device-pointing-stick>', function() {
+  let perDevicePointingStickPage: SettingsPerDevicePointingStickElement;
+
+  setup(async function initializePerDevicePointingStickPage() {
+    perDevicePointingStickPage =
+        document.createElement('settings-per-device-pointing-stick');
+    assert(perDevicePointingStickPage);
+    perDevicePointingStickPage.set('pointingSticks', fakePointingSticks);
+    document.body.appendChild(perDevicePointingStickPage);
+    await flushTasks();
+  });
+
+  teardown(() => {
+    perDevicePointingStickPage.remove();
+  });
+
+  test('PointingStick page updates with new pointing sticks', async () => {
+    let subsections = perDevicePointingStickPage.shadowRoot!.querySelectorAll(
+        'settings-per-device-pointing-stick-subsection');
+    assertEquals(fakePointingSticks.length, subsections.length);
+
+    // Check the number of subsections when the pointing stick list is updated.
+    perDevicePointingStickPage.set('pointingSticks', fakePointingSticks2);
+    await flushTasks();
+    subsections = perDevicePointingStickPage.shadowRoot!.querySelectorAll(
+        'settings-per-device-pointing-stick-subsection');
+    assertEquals(fakePointingSticks2.length, subsections.length);
+  });
+
+  test(
+      'Display correct name used for internal/external pointing sticks', () => {
+        const subsections =
+            perDevicePointingStickPage.shadowRoot!.querySelectorAll(
+                'settings-per-device-pointing-stick-subsection');
+        for (let i = 0; i < subsections.length; i++) {
+          const name =
+              subsections[i]!.shadowRoot!.querySelector('h2')!.textContent;
+          if (fakePointingSticks[i]!.isExternal) {
+            assertEquals(fakePointingSticks[i]!.name, name);
+          } else {
+            assertTrue(subsections[i]!.i18nExists('builtInPointingStickName'));
+            assertEquals('Built-in TrackPoint', name);
+          }
+        }
+      });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/os_printing_page_tests.js b/chrome/test/data/webui/settings/chromeos/os_printing_page/os_printing_page_tests.ts
similarity index 60%
rename from chrome/test/data/webui/settings/chromeos/os_printing_page_tests.js
rename to chrome/test/data/webui/settings/chromeos/os_printing_page/os_printing_page_tests.ts
index 76636667..6743be7 100644
--- a/chrome/test/data/webui/settings/chromeos/os_printing_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/os_printing_page/os_printing_page_tests.ts
@@ -4,19 +4,22 @@
 
 import 'chrome://os-settings/chromeos/lazy_load.js';
 
+import {OsSettingsPrintingPageElement} from 'chrome://os-settings/chromeos/lazy_load.js';
 import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
-import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {getDeepActiveElement} from 'chrome://resources/js/util_ts.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 
-import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+suite('<os-settings-printing-page>', function() {
+  let printingPage: OsSettingsPrintingPageElement;
 
-suite('PrintingPageTests', function() {
-  /** @type {SettingsPrintingPageElement} */
-  let printingPage = null;
-
-  setup(function() {
-    PolymerTest.clearBody();
+  setup(async function() {
+    printingPage = document.createElement('os-settings-printing-page');
+    assert(printingPage);
+    document.body.appendChild(printingPage);
+    await flushTasks();
   });
 
   teardown(function() {
@@ -24,20 +27,7 @@
     Router.getInstance().resetRouteForTesting();
   });
 
-  /**
-   * Set up printing page with loadTimeData overrides
-   * @return {!Promise}
-   */
-  function initializePrintingPage() {
-    printingPage = /** @type {!SettingsPrintingPageElement} */ (
-        document.createElement('os-settings-printing-page'));
-    assertTrue(!!printingPage);
-    document.body.appendChild(printingPage);
-    return flushTasks();
-  }
-
   test('Deep link to print jobs', async () => {
-    await initializePrintingPage();
     const params = new URLSearchParams();
     params.append('settingId', '1402');
     Router.getInstance().navigateTo(routes.OS_PRINTING, params);
@@ -45,8 +35,9 @@
     flush();
 
     const deepLinkElement =
-        printingPage.shadowRoot.querySelector('#printManagement')
-            .shadowRoot.querySelector('cr-icon-button');
+        printingPage.shadowRoot!.querySelector('#printManagement')!.shadowRoot!
+            .querySelector('cr-icon-button');
+    assert(deepLinkElement);
     await waitAfterNextRender(deepLinkElement);
     assertEquals(
         deepLinkElement, getDeepActiveElement(),
@@ -54,8 +45,6 @@
   });
 
   test('Deep link to scanning app', async () => {
-    await initializePrintingPage();
-
     const params = new URLSearchParams();
     params.append('settingId', '1403');
     Router.getInstance().navigateTo(routes.OS_PRINTING, params);
@@ -63,8 +52,9 @@
     flush();
 
     const deepLinkElement =
-        printingPage.shadowRoot.querySelector('#scanningApp')
-            .shadowRoot.querySelector('cr-icon-button');
+        printingPage.shadowRoot!.querySelector('#scanningApp')!.shadowRoot!
+            .querySelector('cr-icon-button');
+    assert(deepLinkElement);
     await waitAfterNextRender(deepLinkElement);
     assertEquals(
         deepLinkElement, getDeepActiveElement(),
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
index d11ad860..b95c48b 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_v3_browsertest.js
@@ -263,6 +263,11 @@
  ['DateTimePageTimezoneSubpage', 'date_time_page/timezone_subpage_test.js'],
  ['DevicePagePerDeviceMouse', 'device_page/per_device_mouse_test.js'],
  [
+   'DevicePagePerDevicePointingStick',
+   'device_page/per_device_pointing_stick_test.js',
+   {enabled: ['ash::features::kInputDeviceSettingsSplit']}
+ ],
+ [
    'DisplayAndMagnificationPage',
    'display_and_magnification_page_tests.js',
  ],
@@ -415,6 +420,7 @@
  ['OsPairedBluetoothList', 'os_paired_bluetooth_list_tests.js'],
  ['OsPairedBluetoothListItem', 'os_paired_bluetooth_list_item_tests.js'],
  ['OsPeoplePageAddUserDialog', 'os_people_page/add_user_dialog_tests.js'],
+ ['OsPrintingPage', 'os_printing_page/os_printing_page_tests.js'],
  ['OsSettingsPage', 'os_settings_page_test.js'],
  ['OsSettingsUi', 'os_settings_ui/os_settings_ui_test.js'],
  ['OsSettingsUiAboutPage', 'os_settings_ui/os_settings_ui_about_page_test.js'],
@@ -463,10 +469,6 @@
    {enabled: ['ash::features::kInputDeviceSettingsSplit']}
  ],
  [
-   'PerDevicePointingStick', 'per_device_pointing_stick_test.js',
-   {enabled: ['ash::features::kInputDeviceSettingsSplit']}
- ],
- [
    'PerDevicePointingStickSubsection',
    'per_device_pointing_stick_subsection_test.js',
    {enabled: ['ash::features::kInputDeviceSettingsSplit']}
@@ -484,7 +486,6 @@
    'PersonalizationPageWithPersonalizationHub',
    'personalization_page_with_personalization_hub_test.js',
  ],
- ['PrintingPage', 'os_printing_page_tests.js'],
  [
    'PrivacyHubSubpage',
    'privacy_hub_subpage_tests.js',
diff --git a/chrome/test/data/webui/settings/chromeos/per_device_keyboard_subsection_test.js b/chrome/test/data/webui/settings/chromeos/per_device_keyboard_subsection_test.js
index 7b9beb43..7c1a2b78 100644
--- a/chrome/test/data/webui/settings/chromeos/per_device_keyboard_subsection_test.js
+++ b/chrome/test/data/webui/settings/chromeos/per_device_keyboard_subsection_test.js
@@ -10,7 +10,7 @@
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
-const KEYBOARD_AUTO_REPEAT_SETTING_ID = 412;
+const KEYBOARD_FUNCTION_KEYS_SETTING_ID = 411;
 
 suite('PerDeviceKeyboardSubsection', function() {
   /**
@@ -95,39 +95,6 @@
     assertEquals(
         updatedKeyboards[0].settings.suppressMetaFkeyRewrites,
         blockMetaFunctionKeyRewritesButton.pref.value);
-
-    const enableAutoRepeatButton =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
-    enableAutoRepeatButton.click();
-    await flushTasks();
-    updatedKeyboards = await getConnectedKeyboardSettings();
-    assertEquals(
-        updatedKeyboards[0].settings.autoRepeatEnabled,
-        enableAutoRepeatButton.pref.value);
-
-    const delaySlider =
-        assert(subsection.shadowRoot.querySelector('#delaySlider'));
-    MockInteractions.pressAndReleaseKeyOn(
-        delaySlider.shadowRoot.querySelector('cr-slider'), 39 /* right */, [],
-        'ArrowRight');
-    await flushTasks();
-    updatedKeyboards = await getConnectedKeyboardSettings();
-    assertEquals(
-        Number(updatedKeyboards[0].settings.autoRepeatDelay.microseconds) /
-            1000,
-        delaySlider.pref.value);
-
-    const repeatRateSlider =
-        assert(subsection.shadowRoot.querySelector('#repeatRateSlider'));
-    MockInteractions.pressAndReleaseKeyOn(
-        repeatRateSlider.shadowRoot.querySelector('cr-slider'), 39 /* right */,
-        [], 'ArrowRight');
-    await flushTasks();
-    updatedKeyboards = await getConnectedKeyboardSettings();
-    assertEquals(
-        Number(updatedKeyboards[0].settings.autoRepeatInterval.microseconds) /
-            1000,
-        repeatRateSlider.pref.value);
   });
 
   /**Test that keyboard settings data are from the keyboard provider.*/
@@ -151,22 +118,6 @@
         subsection.shadowRoot.querySelector(
             '#internalTopRowAreFunctionKeysButton');
     assertFalse(isVisible(internalTopRowAreFunctionKeysButton));
-    let enableAutoRepeatButton =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
-    assertEquals(
-        fakeKeyboards[0].settings.autoRepeatEnabled,
-        enableAutoRepeatButton.pref.value);
-    let delaySlider =
-        assert(subsection.shadowRoot.querySelector('#delaySlider'));
-    assertEquals(
-        Number(fakeKeyboards[0].settings.autoRepeatDelay.microseconds) / 1000,
-        delaySlider.pref.value);
-    let repeatRateSlider =
-        assert(subsection.shadowRoot.querySelector('#repeatRateSlider'));
-    assertEquals(
-        Number(fakeKeyboards[0].settings.autoRepeatInterval.microseconds) /
-            1000,
-        repeatRateSlider.pref.value);
 
     changeKeyboardState(fakeKeyboards[1]);
     externalTopRowAreFunctionKeysButton = subsection.shadowRoot.querySelector(
@@ -181,21 +132,6 @@
     assertEquals(
         fakeKeyboards[1].settings.topRowAreFkeys,
         internalTopRowAreFunctionKeysButton.pref.value);
-    enableAutoRepeatButton =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
-    assertEquals(
-        fakeKeyboards[1].settings.autoRepeatEnabled,
-        enableAutoRepeatButton.pref.value);
-    delaySlider = assert(subsection.shadowRoot.querySelector('#delaySlider'));
-    assertEquals(
-        Number(fakeKeyboards[1].settings.autoRepeatDelay.microseconds) / 1000,
-        delaySlider.pref.value);
-    repeatRateSlider =
-        assert(subsection.shadowRoot.querySelector('#repeatRateSlider'));
-    assertEquals(
-        Number(fakeKeyboards[1].settings.autoRepeatInterval.microseconds) /
-            1000,
-        repeatRateSlider.pref.value);
   });
 
   /**
@@ -259,26 +195,6 @@
         subsection.shadowRoot.querySelector(
             '#externalTopRowAreFunctionKeysButton');
     assertTrue(!!externalTopRowAreFunctionKeysButton);
-
-    // Verify the enable auto-repeat toggle button is in the page.
-    const enableAutoRepeatButton =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
-    assertTrue(!!enableAutoRepeatButton);
-    enableAutoRepeatButton.click();
-
-    // Verify the repeat delay settings box is in the page.
-    const repeatDelayLabel =
-        subsection.shadowRoot.querySelector('#repeatDelayLabel');
-    assertTrue(!!repeatDelayLabel);
-    assertEquals('Delay before repeat', repeatDelayLabel.textContent.trim());
-    assertTrue(!!subsection.shadowRoot.querySelector('#delaySlider'));
-
-    // Verify the repeat rate settings box is in the page.
-    const repeatRateLabel =
-        subsection.shadowRoot.querySelector('#repeatRateLabel');
-    assertTrue(!!repeatRateLabel);
-    assertEquals('Repeat rate', repeatRateLabel.textContent.trim());
-    assertTrue(!!subsection.shadowRoot.querySelector('#repeatRateSlider'));
   });
 
   /**
@@ -343,21 +259,22 @@
    */
   test('deep linking mixin focus on the first searched element', async () => {
     await initializePerDeviceKeyboardSubsection();
-    const autoRepeatToggle =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
+    const topRowAreFunctionKeysToggle = subsection.shadowRoot.querySelector(
+        '#externalTopRowAreFunctionKeysButton');
     subsection.keyboardIndex = 0;
     // Enter the page from auto repeat search tag.
     const url = new URLSearchParams(
         'search=keyboard&settingId=' +
-        encodeURIComponent(KEYBOARD_AUTO_REPEAT_SETTING_ID));
+        encodeURIComponent(KEYBOARD_FUNCTION_KEYS_SETTING_ID));
 
     await Router.getInstance().navigateTo(
         routes.PER_DEVICE_KEYBOARD,
         /* dynamicParams= */ url, /* removeSearch= */ true);
 
-    await waitAfterNextRender(autoRepeatToggle);
-    assertTrue(!!autoRepeatToggle);
-    assertEquals(subsection.shadowRoot.activeElement, autoRepeatToggle);
+    await waitAfterNextRender(topRowAreFunctionKeysToggle);
+    assertTrue(!!topRowAreFunctionKeysToggle);
+    assertEquals(
+        subsection.shadowRoot.activeElement, topRowAreFunctionKeysToggle);
   });
 
   /**
@@ -366,20 +283,20 @@
    */
   test('deep linkng mixin does not focus on second element', async () => {
     await initializePerDeviceKeyboardSubsection();
-    const autoRepeatToggle =
-        subsection.shadowRoot.querySelector('#enableAutoRepeatButton');
+    const topRowAreFunctionKeysToggle = subsection.shadowRoot.querySelector(
+        '#externalTopRowAreFunctionKeysButton');
     subsection.keyboardIndex = 1;
     // Enter the page from auto repeat search tag.
     const url = new URLSearchParams(
         'search=keyboard&settingId=' +
-        encodeURIComponent(KEYBOARD_AUTO_REPEAT_SETTING_ID));
+        encodeURIComponent(KEYBOARD_FUNCTION_KEYS_SETTING_ID));
 
     await Router.getInstance().navigateTo(
         routes.PER_DEVICE_KEYBOARD,
         /* dynamicParams= */ url, /* removeSearch= */ true);
     await flushTasks();
 
-    assertTrue(!!autoRepeatToggle);
+    assertTrue(!!topRowAreFunctionKeysToggle);
     assertFalse(!!subsection.shadowRoot.activeElement);
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/per_device_pointing_stick_test.js b/chrome/test/data/webui/settings/chromeos/per_device_pointing_stick_test.js
deleted file mode 100644
index 9214704..0000000
--- a/chrome/test/data/webui/settings/chromeos/per_device_pointing_stick_test.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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 {fakePointingSticks, fakePointingSticks2, SettingsPerDevicePointingStickElement} from 'chrome://os-settings/chromeos/os_settings.js';
-import {assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-
-suite('PerDevicePointingStick', function() {
-  /**
-   * @type {?SettingsPerDevicePointingStickElement}
-   */
-  let perDevicePointingStickPage = null;
-
-  setup(() => {
-    PolymerTest.clearBody();
-  });
-
-  teardown(() => {
-    perDevicePointingStickPage = null;
-  });
-
-  function initializePerDevicePointingStickPage(
-      pointingSticks = fakePointingSticks) {
-    perDevicePointingStickPage =
-        document.createElement('settings-per-device-pointing-stick');
-    assertTrue(perDevicePointingStickPage != null);
-    perDevicePointingStickPage.pointingSticks = pointingSticks;
-    document.body.appendChild(perDevicePointingStickPage);
-    return flushTasks();
-  }
-
-  test('PointingStick page updates with new pointing sticks', async () => {
-    await initializePerDevicePointingStickPage();
-    let subsections = perDevicePointingStickPage.shadowRoot.querySelectorAll(
-        'settings-per-device-pointing-stick-subsection');
-    assertEquals(fakePointingSticks.length, subsections.length);
-
-    // Check the number of subsections when the pointing stick list is updated.
-    perDevicePointingStickPage.pointingSticks = fakePointingSticks2;
-    await flushTasks();
-    subsections = perDevicePointingStickPage.shadowRoot.querySelectorAll(
-        'settings-per-device-pointing-stick-subsection');
-    assertEquals(fakePointingSticks2.length, subsections.length);
-  });
-
-  test(
-      'Display correct name used for internal/external pointing sticks',
-      async () => {
-        await initializePerDevicePointingStickPage();
-        const subsections =
-            perDevicePointingStickPage.shadowRoot.querySelectorAll(
-                'settings-per-device-pointing-stick-subsection');
-        for (let i = 0; i < subsections.length; i++) {
-          const name =
-              subsections[i].shadowRoot.querySelector('h2').textContent;
-          if (fakePointingSticks[i].isExternal) {
-            assertEquals(fakePointingSticks[i].name, name);
-          } else {
-            assertTrue(subsections[i].i18nExists('builtInPointingStickName'));
-            assertEquals('Built-in TrackPoint', name);
-          }
-        }
-      });
-});
\ No newline at end of file
diff --git a/chrome/updater/app/app_install.cc b/chrome/updater/app/app_install.cc
index 49d2d0c8..9aaf280f 100644
--- a/chrome/updater/app/app_install.cc
+++ b/chrome/updater/app/app_install.cc
@@ -132,7 +132,7 @@
       tag_parsing_result.tag_args.value_or(tagging::TagArgs());
   if (!tag_args.apps.empty()) {
     // TODO(crbug.com/1128631): support bundles. For now, assume one app.
-    DCHECK_EQ(tag_args.apps.size(), size_t{1});
+    CHECK_EQ(tag_args.apps.size(), size_t{1});
     const tagging::AppArgs& app_args = tag_args.apps.front();
     app_id_ = app_args.app_id;
     app_name_ = app_args.app_name;
diff --git a/chrome/updater/app/app_install_win.cc b/chrome/updater/app/app_install_win.cc
index bb6e33d8..6b45b7b 100644
--- a/chrome/updater/app/app_install_win.cc
+++ b/chrome/updater/app/app_install_win.cc
@@ -196,9 +196,9 @@
   // by the progress window. This call always occurs in the context of the
   // thread which owns the window.
   void Invoke(WPARAM wparam, LPARAM lparam) {
-    DCHECK_EQ(observer_thread_id_, ::GetCurrentThreadId());
-    DCHECK_NE(lparam, 0);
-    DCHECK_EQ(wparam, WPARAM{0});
+    CHECK_EQ(observer_thread_id_, ::GetCurrentThreadId());
+    CHECK_NE(lparam, 0);
+    CHECK_EQ(wparam, WPARAM{0});
     std::unique_ptr<base::OnceClosure> callback_wrapper(
         reinterpret_cast<base::OnceClosure*>(lparam));
     std::move(*callback_wrapper).Run();
@@ -886,7 +886,7 @@
 
 void AppInstallControllerImpl::RunUI() {
   CHECK(ui_task_runner_->RunsTasksInCurrentSequence());
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
 
   ui_message_loop_->Run();
   ui_message_loop_->RemoveMessageFilter(this);
@@ -899,13 +899,13 @@
 }
 
 void AppInstallControllerImpl::DoExit() {
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
   PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0);
 }
 
 BOOL AppInstallControllerImpl::PreTranslateMessage(MSG* msg) {
   if (const auto ui_thread_id = GetUIThreadID(); ui_thread_id != 0) {
-    DCHECK_EQ(ui_thread_id, GetCurrentThreadId());
+    CHECK_EQ(ui_thread_id, GetCurrentThreadId());
   } else {
     VLOG(1) << "Can't find a thread id for the message: " << msg->message;
   }
@@ -917,12 +917,12 @@
 }
 
 DWORD AppInstallControllerImpl::GetUIThreadID() const {
-  DCHECK_NE(ui_thread_id_, 0u);
+  CHECK_NE(ui_thread_id_, 0u);
   return ui_thread_id_;
 }
 
 bool AppInstallControllerImpl::DoLaunchBrowser(const std::string& url) {
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
 
   return SUCCEEDED(RunDeElevated(base::SysUTF8ToWide(url), {}));
 }
@@ -930,17 +930,17 @@
 bool AppInstallControllerImpl::DoRestartBrowser(
     bool restart_all_browsers,
     const std::vector<std::u16string>& urls) {
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
   return false;
 }
 
 bool AppInstallControllerImpl::DoReboot() {
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
   return false;
 }
 
 void AppInstallControllerImpl::DoCancel() {
-  DCHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
+  CHECK_EQ(GetUIThreadID(), GetCurrentThreadId());
   main_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(&UpdateService::CancelInstalls, update_service_, app_id_));
diff --git a/chrome/updater/app/server/win/com_classes_legacy.cc b/chrome/updater/app/server/win/com_classes_legacy.cc
index 418bf1f..f562990 100644
--- a/chrome/updater/app/server/win/com_classes_legacy.cc
+++ b/chrome/updater/app/server/win/com_classes_legacy.cc
@@ -569,7 +569,7 @@
       }
 
     } else if (result_) {
-      DCHECK_NE(result_.value(), UpdateService::Result::kSuccess);
+      CHECK_NE(result_.value(), UpdateService::Result::kSuccess);
       state_value = STATE_ERROR;
       error_code =
           (result_.value() == UpdateService::Result::kSuccess) ? 0 : -1;
diff --git a/chrome/updater/app/server/win/server.cc b/chrome/updater/app/server/win/server.cc
index bda0aef..c8c4538f 100644
--- a/chrome/updater/app/server/win/server.cc
+++ b/chrome/updater/app/server/win/server.cc
@@ -367,6 +367,12 @@
     LOG_IF(ERROR,
            UninstallGoogleUpdate(updater_scope(), temp_dir->GetPath(),
                                  UpdaterScopeToHKeyRoot(updater_scope())));
+
+    // TODO(crbug.com/1425609) - revert the CL that introduced this logging
+    // after the bug is resolved.
+    for (const auto& clsid : GetServers(false, updater_scope())) {
+      LogClsidEntries(clsid);
+    }
   }
 
   return succeeded;
diff --git a/chrome/updater/app/server/win/service_main.cc b/chrome/updater/app/server/win/service_main.cc
index c3de27d0..d6311e80 100644
--- a/chrome/updater/app/server/win/service_main.cc
+++ b/chrome/updater/app/server/win/service_main.cc
@@ -10,6 +10,7 @@
 #include <string>
 #include <type_traits>
 
+#include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "base/no_destructor.h"
@@ -60,7 +61,7 @@
   }
 
   int ret = service->Start();
-  DCHECK_NE(ret, int{STILL_ACTIVE});
+  CHECK_NE(ret, int{STILL_ACTIVE});
   return ret;
 }
 
diff --git a/chrome/updater/ipc/proxy_impl_base_win.h b/chrome/updater/ipc/proxy_impl_base_win.h
index 4a7286a..252a453 100644
--- a/chrome/updater/ipc/proxy_impl_base_win.h
+++ b/chrome/updater/ipc/proxy_impl_base_win.h
@@ -84,6 +84,11 @@
         }
         VLOG(2) << "::CoCreateInstance failed: "
                 << base::win::WStringFromGUID(clsid) << ": " << std::hex << hr;
+
+        // TODO(crbug.com/1425609) - revert the CL that introduced this logging
+        // after the bug is resolved.
+        LogClsidEntries(clsid);
+
         if (hr == REGDB_E_CLASSNOTREG) {
           return base::unexpected(hr);
         }
diff --git a/chrome/updater/ipc/update_service_internal_proxy_win.cc b/chrome/updater/ipc/update_service_internal_proxy_win.cc
index dba73da..f6653ba 100644
--- a/chrome/updater/ipc/update_service_internal_proxy_win.cc
+++ b/chrome/updater/ipc/update_service_internal_proxy_win.cc
@@ -53,7 +53,7 @@
 
  private:
   ~UpdaterInternalCallback() override {
-    DCHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
+    CHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
     if (callback_)
       std::move(callback_).Run();
   }
@@ -66,13 +66,13 @@
 };
 
 IFACEMETHODIMP UpdaterInternalCallback::Run(LONG result) {
-  DCHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
+  CHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
   VLOG(2) << __func__ << " result " << result << ".";
   return S_OK;
 }
 
 base::OnceClosure UpdaterInternalCallback::Disconnect() {
-  DCHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
+  CHECK_EQ(base::PlatformThreadRef(), com_thread_ref_);
   VLOG(2) << __func__;
   return std::move(callback_);
 }
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index b83a113..ab77a46f 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -604,6 +604,13 @@
 }
 
 TEST_F(IntegrationTest, CheckForUpdate) {
+#if BUILDFLAG(IS_WIN)
+  // TODO(crbug.com/1425609): Remove procmon logging once bug is fixed.
+  const base::ScopedClosureRunner stop_procmon_logging(
+      base::BindOnce(&updater::test::StopProcmonLogging,
+                     updater::test::StartProcmonLogging()));
+#endif  // #if BUILDFLAG(IS_WIN)
+
   ScopedServer test_server(test_commands_);
   ASSERT_NO_FATAL_FAILURE(Install());
 
diff --git a/chrome/updater/updater_scope.cc b/chrome/updater/updater_scope.cc
index 0d02a500..1c97af57 100644
--- a/chrome/updater/updater_scope.cc
+++ b/chrome/updater/updater_scope.cc
@@ -50,7 +50,7 @@
       GetTagArgsForCommandLine(command_line).tag_args;
   if (tag_args && !tag_args->apps.empty() &&
       tag_args->apps.front().needs_admin) {
-    DCHECK_EQ(tag_args->apps.size(), size_t{1});
+    CHECK_EQ(tag_args->apps.size(), size_t{1});
     switch (*tag_args->apps.front().needs_admin) {
       case tagging::AppArgs::NeedsAdmin::kYes:
         return UpdaterScope::kSystem;
diff --git a/chrome/updater/util/util.cc b/chrome/updater/util/util.cc
index 8d5d473c..0783af2 100644
--- a/chrome/updater/util/util.cc
+++ b/chrome/updater/util/util.cc
@@ -13,6 +13,7 @@
 #endif  // BUILDFLAG(IS_WIN)
 
 #include "base/base_paths.h"
+#include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
@@ -55,8 +56,8 @@
 
 const char kHexString[] = "0123456789ABCDEF";
 inline char IntToHex(int i) {
-  DCHECK_GE(i, 0) << i << " not a hex value";
-  DCHECK_LE(i, 15) << i << " not a hex value";
+  CHECK_GE(i, 0) << i << " not a hex value";
+  CHECK_LE(i, 15) << i << " not a hex value";
   return kHexString[i];
 }
 
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index c2c1d87..a9139a3 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -20,6 +20,7 @@
 
 #include "base/base_paths_win.h"
 #include "base/check.h"
+#include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/containers/flat_map.h"
 #include "base/cxx17_backports.h"
@@ -50,6 +51,7 @@
 #include "base/win/scoped_process_information.h"
 #include "base/win/scoped_variant.h"
 #include "base/win/startup_information.h"
+#include "base/win/win_util.h"
 #include "chrome/updater/constants.h"
 #include "chrome/updater/updater_branding.h"
 #include "chrome/updater/updater_scope.h"
@@ -207,7 +209,7 @@
 HMODULE GetModuleHandleFromAddress(void* address) {
   MEMORY_BASIC_INFORMATION mbi = {0};
   size_t result = ::VirtualQuery(address, &mbi, sizeof(mbi));
-  DCHECK_EQ(result, sizeof(mbi));
+  CHECK_EQ(result, sizeof(mbi));
   return static_cast<HMODULE>(mbi.AllocationBase);
 }
 
@@ -388,7 +390,7 @@
 int GetDownloadProgress(int64_t downloaded_bytes, int64_t total_bytes) {
   if (downloaded_bytes == -1 || total_bytes == -1 || total_bytes == 0)
     return -1;
-  DCHECK_LE(downloaded_bytes, total_bytes);
+  CHECK_LE(downloaded_bytes, total_bytes);
   return 100 * base::clamp(static_cast<double>(downloaded_bytes) / total_bytes,
                            0.0, 1.0);
 }
@@ -405,7 +407,7 @@
     return token_handle;
   }
 
-  DCHECK_EQ(bytes_returned, sizeof(*session_id_ptr));
+  CHECK_EQ(bytes_returned, sizeof(*session_id_ptr));
   DWORD session_id = *session_id_ptr;
   ::WTSFreeMemory(session_id_ptr);
   VLOG(1) << "::WTSQuerySessionInformation session id: " << session_id;
@@ -1098,4 +1100,27 @@
                                 : ::IsUserAnAdmin() && IsUACOn();
 }
 
+void LogClsidEntries(REFCLSID clsid) {
+  const std::wstring local_server32_reg_path(
+      base::StrCat({base::StrCat({L"Software\\Classes\\CLSID\\",
+                                  base::win::WStringFromGUID(clsid)}),
+                    L"\\LocalServer32"}));
+
+  for (const HKEY root : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
+    for (const REGSAM key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
+      base::win::RegKey key;
+      if (ERROR_SUCCESS == key.Open(root, local_server32_reg_path.c_str(),
+                                    KEY_QUERY_VALUE | key_flag)) {
+        std::wstring val;
+        if (ERROR_SUCCESS == key.ReadValue(L"", &val)) {
+          LOG(ERROR) << __func__ << ": CLSID entry found: "
+                     << (root == HKEY_LOCAL_MACHINE ? "HKEY_LOCAL_MACHINE\\"
+                                                    : "HKEY_CURRENT_USER\\")
+                     << local_server32_reg_path << ": " << val;
+        }
+      }
+    }
+  }
+}
+
 }  // namespace updater
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h
index c8746fe..92844c7 100644
--- a/chrome/updater/util/win_util.h
+++ b/chrome/updater/util/win_util.h
@@ -24,6 +24,7 @@
 #include "base/types/expected.h"
 #include "base/win/atl.h"
 #include "base/win/scoped_handle.h"
+#include "base/win/win_util.h"
 #include "base/win/windows_types.h"
 #include "chrome/updater/updater_scope.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -370,6 +371,10 @@
 // Deletes `service_name` system service and returns `true` on success.
 [[nodiscard]] bool DeleteService(const std::wstring& service_name);
 
+// Logs CLSID entries in HKLM and HKCU under both the 64-bit and 32-bit hives
+// for the given CLSID.
+void LogClsidEntries(REFCLSID clsid);
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_UTIL_WIN_UTIL_H_
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index a89f754..e04fa0c 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -474,4 +474,12 @@
   }
 }
 
+TEST(WinUtil, LogClsidEntries) {
+  CLSID clsid = {};
+  EXPECT_HRESULT_SUCCEEDED(
+      ::CLSIDFromProgID(L"InternetExplorer.Application", &clsid));
+
+  LogClsidEntries(clsid);
+}
+
 }  // namespace updater
diff --git a/chrome/updater/win/installer_api.cc b/chrome/updater/win/installer_api.cc
index 2c811db..29daea51 100644
--- a/chrome/updater/win/installer_api.cc
+++ b/chrome/updater/win/installer_api.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
 #include "base/cxx17_backports.h"
@@ -307,7 +308,7 @@
         if (installer_outcome->installer_cmd_line) {
           result.installer_cmd_line = *installer_outcome->installer_cmd_line;
         }
-        DCHECK_EQ(result.error, 0);
+        CHECK_EQ(result.error, 0);
         break;
 
       case InstallerResult::kCustomError:
@@ -328,7 +329,7 @@
         if (installer_outcome->installer_text) {
           result.installer_text = *installer_outcome->installer_text;
         }
-        DCHECK_NE(result.error, 0);
+        CHECK_NE(result.error, 0);
         break;
 
       case InstallerResult::kMsiError:
@@ -345,7 +346,7 @@
           result.extended_error = *installer_outcome->installer_extracode1;
         }
         result.installer_text = GetTextForSystemError(result.error);
-        DCHECK_NE(result.error, 0);
+        CHECK_NE(result.error, 0);
         break;
 
       case InstallerResult::kExitCode:
diff --git a/chrome/updater/win/protocol_parser_xml.cc b/chrome/updater/win/protocol_parser_xml.cc
index 0a01b9c..07d632d5 100644
--- a/chrome/updater/win/protocol_parser_xml.cc
+++ b/chrome/updater/win/protocol_parser_xml.cc
@@ -14,6 +14,7 @@
 #include <string>
 
 #include "base/check.h"
+#include "base/check_op.h"
 #include "base/containers/flat_map.h"
 #include "base/strings/string_number_conversions_win.h"
 #include "base/strings/sys_string_conversions.h"
@@ -48,7 +49,7 @@
     return false;
   }
 
-  DCHECK_EQ(node_value.type(), VT_BSTR);
+  CHECK_EQ(node_value.type(), VT_BSTR);
   VARIANT released_variant = node_value.Release();
   *value = V_BSTR(&released_variant);
   return true;
diff --git a/chrome/updater/win/scoped_impersonation.cc b/chrome/updater/win/scoped_impersonation.cc
index bbcdeb0c..22b27d48 100644
--- a/chrome/updater/win/scoped_impersonation.cc
+++ b/chrome/updater/win/scoped_impersonation.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/updater/win/scoped_impersonation.h"
 
+#include "base/check.h"
+#include "base/check_op.h"
 #include "base/logging.h"
 #include "chrome/updater/util/win_util.h"
 
@@ -14,7 +16,7 @@
     return E_FAIL;
 
   result_ = ::ImpersonateLoggedOnUser(token) ? S_OK : HRESULTFromLastError();
-  DCHECK_EQ(result_, S_OK);
+  CHECK_EQ(result_, S_OK);
   return result_;
 }
 
diff --git a/chrome/updater/win/task_scheduler.cc b/chrome/updater/win/task_scheduler.cc
index 84d6a81e..d41e623 100644
--- a/chrome/updater/win/task_scheduler.cc
+++ b/chrome/updater/win/task_scheduler.cc
@@ -85,7 +85,7 @@
   CHECK(user_name);
   ULONG user_name_size = 256;
   // Paranoia... ;-)
-  DCHECK_EQ(sizeof(OLECHAR), sizeof(WCHAR));
+  CHECK_EQ(sizeof(OLECHAR), sizeof(WCHAR));
   if (!::GetUserNameExW(
           NameSamCompatible,
           user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
@@ -98,7 +98,7 @@
             NameSamCompatible,
             user_name->AllocateBytes(user_name_size * sizeof(OLECHAR)),
             &user_name_size)) {
-      DCHECK_NE(DWORD{ERROR_MORE_DATA}, ::GetLastError());
+      CHECK_NE(DWORD{ERROR_MORE_DATA}, ::GetLastError());
       PLOG(ERROR) << "GetUserNameEx failed.";
       return false;
     }
diff --git a/chrome/updater/win/ui/splash_screen.cc b/chrome/updater/win/ui/splash_screen.cc
index 11fe7f8..6b6d037 100644
--- a/chrome/updater/win/ui/splash_screen.cc
+++ b/chrome/updater/win/ui/splash_screen.cc
@@ -62,7 +62,7 @@
 
 void SplashScreen::Show() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_EQ(WindowState::STATE_CREATED, state_);
+  CHECK_EQ(WindowState::STATE_CREATED, state_);
 
   if (FAILED(Initialize())) {
     return;
@@ -199,7 +199,7 @@
 LRESULT SplashScreen::OnTimer(UINT, WPARAM, LPARAM, BOOL& handled) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK_EQ(state_, WindowState::STATE_FADING);
-  DCHECK_GT(alpha_index_, 0);
+  CHECK_GT(alpha_index_, 0);
   if (--alpha_index_) {
     ::SetLayeredWindowAttributes(
         m_hWnd, 0, AlphaScaleToAlphaValue(kAlphaScales[alpha_index_]),
diff --git a/chrome/updater/win/ui/ui.cc b/chrome/updater/win/ui/ui.cc
index 3d972a2..ea6077f 100644
--- a/chrome/updater/win/ui/ui.cc
+++ b/chrome/updater/win/ui/ui.cc
@@ -5,9 +5,9 @@
 #include "chrome/updater/win/ui/ui.h"
 
 #include <stdint.h>
-#include <functional>
 
 #include "base/check.h"
+#include "base/check_op.h"
 #include "base/logging.h"
 #include "chrome/updater/updater_scope.h"
 #include "chrome/updater/util/win_util.h"
@@ -158,7 +158,7 @@
 
 // Called when ESC key is pressed.
 LRESULT OmahaWnd::OnCancel(WORD, WORD id, HWND, BOOL& handled) {
-  DCHECK_EQ(id, IDCANCEL);
+  CHECK_EQ(id, IDCANCEL);
 
   if (!is_close_enabled_) {
     return 0;
@@ -242,7 +242,7 @@
 
 HRESULT InitializeCommonControls(DWORD control_classes) {
   INITCOMMONCONTROLSEX init_ctrls = {sizeof(INITCOMMONCONTROLSEX), 0};
-  DCHECK_EQ(init_ctrls.dwSize, sizeof(init_ctrls));
+  CHECK_EQ(init_ctrls.dwSize, sizeof(init_ctrls));
   init_ctrls.dwICC = control_classes;
   if (!::InitCommonControlsEx(&init_ctrls)) {
     const DWORD error = ::GetLastError();
diff --git a/chrome/updater/win/ui/ui_util.h b/chrome/updater/win/ui/ui_util.h
index 21e41a4..3bdb50d 100644
--- a/chrome/updater/win/ui/ui_util.h
+++ b/chrome/updater/win/ui/ui_util.h
@@ -48,7 +48,7 @@
 // short, unsigned long, unsigned int etc.
 template <typename T>
 inline T CeilingDivide(T m, T n) {
-  DCHECK_NE(0, n);
+  CHECK_NE(0, n);
   return (m + n - 1) / n;
 }
 
diff --git a/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc b/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
index 2348770..9721199 100644
--- a/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
+++ b/chromeos/ash/components/phonehub/fake_phone_hub_manager.cc
@@ -103,15 +103,5 @@
   return &app_stream_manager_;
 }
 
-eche_app::EcheConnectionStatusHandler*
-FakePhoneHubManager::GetEcheConnectionStatusHandler() {
-  return eche_connection_status_handler_;
-}
-
-void FakePhoneHubManager::SetEcheConnectionStatusHandler(
-    eche_app::EcheConnectionStatusHandler* eche_connection_status_handler) {
-  eche_connection_status_handler_ = eche_connection_status_handler;
-}
-
 }  // namespace phonehub
 }  // namespace ash
diff --git a/chromeos/ash/components/phonehub/fake_phone_hub_manager.h b/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
index 82e2c82..75e8517 100644
--- a/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
+++ b/chromeos/ash/components/phonehub/fake_phone_hub_manager.h
@@ -105,11 +105,6 @@
     host_last_seen_timestamp_ = timestamp;
   }
 
-  void set_eche_connection_hander(
-      eche_app::EcheConnectionStatusHandler* handler) {
-    eche_connection_status_handler_ = handler;
-  }
-
  private:
   // PhoneHubManager:
   BrowserTabsModelProvider* GetBrowserTabsModelProvider() override;
@@ -134,11 +129,6 @@
       base::OnceCallback<void(absl::optional<base::Time>)> callback) override;
   IconDecoder* GetIconDecoder() override;
   AppStreamManager* GetAppStreamManager() override;
-  eche_app::EcheConnectionStatusHandler* GetEcheConnectionStatusHandler()
-      override;
-  void SetEcheConnectionStatusHandler(
-      eche_app::EcheConnectionStatusHandler* eche_connection_status_handler)
-      override;
 
   FakeDoNotDisturbController fake_do_not_disturb_controller_;
   FakeFeatureStatusProvider fake_feature_status_provider_;
@@ -159,8 +149,6 @@
   FakePingManager fake_ping_manager_;
   FakeIconDecoder fake_icon_decoder_;
   AppStreamManager app_stream_manager_;
-  eche_app::EcheConnectionStatusHandler* eche_connection_status_handler_ =
-      nullptr;
   absl::optional<base::Time> host_last_seen_timestamp_ = absl::nullopt;
 };
 
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager.h b/chromeos/ash/components/phonehub/phone_hub_manager.h
index a12487c..1d41f23 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager.h
+++ b/chromeos/ash/components/phonehub/phone_hub_manager.h
@@ -12,11 +12,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
-
-namespace eche_app {
-class EcheConnectionStatusHandler;
-}
-
 namespace phonehub {
 
 class BrowserTabsModelProvider;
@@ -69,11 +64,6 @@
   virtual UserActionRecorder* GetUserActionRecorder() = 0;
   virtual IconDecoder* GetIconDecoder() = 0;
   virtual AppStreamManager* GetAppStreamManager() = 0;
-  virtual eche_app::EcheConnectionStatusHandler*
-  GetEcheConnectionStatusHandler() = 0;
-  virtual void SetEcheConnectionStatusHandler(
-      eche_app::EcheConnectionStatusHandler*
-          eche_connection_status_handler) = 0;
 
   // Retrieves the timestamp of the last successful discovery for active host,
   // or nullopt if it hasn't been seen in the current Chrome session.
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
index f0ffc2d..93b57452 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
+++ b/chromeos/ash/components/phonehub/phone_hub_manager_impl.cc
@@ -279,18 +279,6 @@
   connection_manager_->GetHostLastSeenTimestamp(std::move(callback));
 }
 
-eche_app::EcheConnectionStatusHandler*
-PhoneHubManagerImpl::GetEcheConnectionStatusHandler() {
-  return eche_connection_status_handler_;
-}
-
-void PhoneHubManagerImpl::SetEcheConnectionStatusHandler(
-    eche_app::EcheConnectionStatusHandler* eche_connection_status_handler) {
-  eche_connection_status_handler_ = eche_connection_status_handler;
-  recent_apps_interaction_handler_->SetConnectionStatusHandler(
-      eche_connection_status_handler_);
-}
-
 // NOTE: These should be destroyed in the opposite order of how these objects
 // are initialized in the constructor.
 void PhoneHubManagerImpl::Shutdown() {
diff --git a/chromeos/ash/components/phonehub/phone_hub_manager_impl.h b/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
index 01dbdac..f2685486 100644
--- a/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
+++ b/chromeos/ash/components/phonehub/phone_hub_manager_impl.h
@@ -91,12 +91,6 @@
   void GetHostLastSeenTimestamp(
       base::OnceCallback<void(absl::optional<base::Time>)> callback) override;
 
-  eche_app::EcheConnectionStatusHandler* GetEcheConnectionStatusHandler()
-      override;
-  void SetEcheConnectionStatusHandler(
-      eche_app::EcheConnectionStatusHandler* eche_connection_status_handler)
-      override;
-
  private:
   // KeyedService:
   void Shutdown() override;
@@ -136,8 +130,6 @@
   std::unique_ptr<FeatureSetupResponseProcessor>
       feature_setup_response_processor_;
   std::unique_ptr<PingManager> ping_manager_;
-  eche_app::EcheConnectionStatusHandler* eche_connection_status_handler_ =
-      nullptr;
 };
 
 }  // namespace phonehub
diff --git a/chromeos/crosapi/mojom/clipboard_history.mojom b/chromeos/crosapi/mojom/clipboard_history.mojom
index 8d24d7b..433c2b1 100644
--- a/chromeos/crosapi/mojom/clipboard_history.mojom
+++ b/chromeos/crosapi/mojom/clipboard_history.mojom
@@ -25,6 +25,8 @@
   [Default] kUnknown,
   // Shown by a toast.
   [MinVersion=1] kToast,
+  // Shown by long-pressing Ctrl+V.
+  [MinVersion=2] kControlVLongpress,
 };
 
 // This interface is implemented by Ash-Chrome.
diff --git a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
index ca5c3bcd..8ea0ebe 100644
--- a/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
+++ b/components/browser_ui/share/android/java/src/org/chromium/components/browser_ui/share/ShareHelper.java
@@ -172,7 +172,6 @@
                 // In case where the receiver is not unregistered correctly, cancel the callback
                 // (to satisfy guarantee that exactly one method of TargetChosenCallback is called).
                 prevReceiver.cancel();
-                prevReceiver.detach();
             }
             TARGET_CHOSEN_RECEIVER_KEY.attachToHost(window.getUnownedUserDataHost(), this);
             mAttachedWindow = new WeakReference<>(window);
@@ -251,10 +250,6 @@
             if (resultCode == Activity.RESULT_CANCELED) {
                 cancel();
             }
-
-            // Always detach this receiver from the attaching context, since the current sharing
-            // journey is completed.
-            detach();
         }
 
         private boolean isUntrustedIntent(Intent intent) {
@@ -281,6 +276,7 @@
                 mCallback.onCancel();
                 mCallback = null;
             }
+            detach();
         }
     }
 
diff --git a/components/creator/BUILD.gn b/components/creator/BUILD.gn
deleted file mode 100644
index 73d824f..0000000
--- a/components/creator/BUILD.gn
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright 2022 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//testing/test.gni")
-
-source_set("creator") {
-  public = [ "public/creator_api.h" ]
-
-  sources = [
-    "creator_api_impl.cc",
-    "creator_api_impl.h",
-    "public/creator_api.cc",
-  ]
-  deps = [
-    "//base",
-    "//url",
-  ]
-
-  public_deps = [ "//base" ]
-}
diff --git a/components/creator/COMMON_METADATA b/components/creator/COMMON_METADATA
deleted file mode 100644
index a65af5b0..0000000
--- a/components/creator/COMMON_METADATA
+++ /dev/null
@@ -1,4 +0,0 @@
-monorail {
-  component: "UI>Browser>Sharing"
-}
-team_email: "creator@chromium.org"
diff --git a/components/creator/DIR_METADATA b/components/creator/DIR_METADATA
deleted file mode 100644
index d324873..0000000
--- a/components/creator/DIR_METADATA
+++ /dev/null
@@ -1 +0,0 @@
-mixins: "//components/creator/COMMON_METADATA"
diff --git a/components/creator/OWNERS b/components/creator/OWNERS
deleted file mode 100644
index 009e2aa..0000000
--- a/components/creator/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-file://chrome/browser/share/OWNERS
diff --git a/components/creator/README.md b/components/creator/README.md
deleted file mode 100644
index e0fbe93..0000000
--- a/components/creator/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Creator Component
-
-The creator component provides a cross-platform implementation of components
-used in the Creator Profile. 
-
-Primary responsibilities:
-* Fetching Channel metadata.
diff --git a/components/creator/creator_api_impl.cc b/components/creator/creator_api_impl.cc
deleted file mode 100644
index f188685c..0000000
--- a/components/creator/creator_api_impl.cc
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/creator/creator_api_impl.h"
-
-#include <string>
-
-#include "components/creator/public/creator_api.h"
-#include "url/gurl.h"
-
-namespace creator {
-Creator CreatorApiImpl::GetCreator(std::string channel_id) {
-  // TODO(crbug.com/1365645) query actual data for given channel id.
-  return Creator{u"alexainsley.com", /*title=*/u"Alex Ainsley"};
-}
-
-void CreatorApiImpl::GetWebId(std::string url,
-                              base::OnceCallback<void(std::string)> callback) {
-  if (url.empty()) {
-    std::move(callback).Run({});
-    return;
-  }
-}
-}  // namespace creator
\ No newline at end of file
diff --git a/components/creator/creator_api_impl.h b/components/creator/creator_api_impl.h
deleted file mode 100644
index 6e7b58c..0000000
--- a/components/creator/creator_api_impl.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_CREATOR_CREATOR_API_IMPL_H_
-#define COMPONENTS_CREATOR_CREATOR_API_IMPL_H_
-
-#include "components/creator/public/creator_api.h"
-
-namespace creator {
-class CreatorApiImpl : public CreatorApi {
- public:
-  CreatorApiImpl();
-  ~CreatorApiImpl() override;
-  CreatorApiImpl(const CreatorApiImpl&) = delete;
-  CreatorApiImpl& operator=(const CreatorApiImpl&) = delete;
-
-  // CreatorApi
-  Creator GetCreator(std::string web_channel_id);
-  void GetWebId(std::string url,
-                base::OnceCallback<void(std::string)> callback);
-};
-
-}  // namespace creator
-
-#endif  // COMPONENTS_CREATOR_CREATOR_API_IMPL_H_
diff --git a/components/creator/public/creator_api.cc b/components/creator/public/creator_api.cc
deleted file mode 100644
index 71ebbbd..0000000
--- a/components/creator/public/creator_api.cc
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/creator/public/creator_api.h"
-
-#include "url/gurl.h"
-
-namespace creator {
-
-CreatorApi::CreatorApi() = default;
-CreatorApi::~CreatorApi() = default;
-
-}  // namespace creator
\ No newline at end of file
diff --git a/components/creator/public/creator_api.h b/components/creator/public/creator_api.h
deleted file mode 100644
index 1800de68..0000000
--- a/components/creator/public/creator_api.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2022 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_CREATOR_PUBLIC_CREATOR_API_H_
-#define COMPONENTS_CREATOR_PUBLIC_CREATOR_API_H_
-
-#include <string>
-
-#include "base/functional/callback.h"
-
-namespace creator {
-
-struct Creator {
-  std::u16string url;
-  std::u16string title;
-};
-
-// This is the public access point for interacting with the creator contents.
-class CreatorApi {
- public:
-  CreatorApi();
-  virtual ~CreatorApi();
-  CreatorApi(const CreatorApi&) = delete;
-  CreatorApi& operator=(const CreatorApi&) = delete;
-
-  Creator GetCreator(std::string web_channel_id);
-  void GetWebId(std::string url,
-                base::OnceCallback<void(std::string)> callback);
-};
-
-}  // namespace creator
-
-#endif  // COMPONENTS_CREATOR_PUBLIC_CREATOR_API_H_
diff --git a/components/image_service/image_service.cc b/components/image_service/image_service.cc
index 1d837c06..eec9d19 100644
--- a/components/image_service/image_service.cc
+++ b/components/image_service/image_service.cc
@@ -159,9 +159,6 @@
   if (opt_guide && base::FeatureList::IsEnabled(
                        kImageServiceOptimizationGuideSalientImages)) {
     opt_guide_ = opt_guide;
-    // OptimizationGuide requires registering all desired types in advance.
-    opt_guide_->RegisterOptimizationTypes(
-        {optimization_guide::proto::SALIENT_IMAGE});
   }
 }
 
diff --git a/components/image_service/image_service_unittest.cc b/components/image_service/image_service_unittest.cc
index 2afc22a..682d14a2 100644
--- a/components/image_service/image_service_unittest.cc
+++ b/components/image_service/image_service_unittest.cc
@@ -115,10 +115,8 @@
   responses->push_back(image_url);
 }
 
-TEST_F(ImageServiceTest, RegisteredSalientImageType) {
-  ASSERT_EQ(test_opt_guide_->registered_optimization_types().size(), 1U);
-  EXPECT_EQ(test_opt_guide_->registered_optimization_types()[0],
-            optimization_guide::proto::SALIENT_IMAGE);
+TEST_F(ImageServiceTest, DoesNotRegisterForNavigationRelatedMetadata) {
+  ASSERT_EQ(test_opt_guide_->registered_optimization_types().size(), 0U);
 }
 
 TEST_F(ImageServiceTest, GetConsentToFetchImage) {
diff --git a/components/omnibox/browser/autocomplete_controller.cc b/components/omnibox/browser/autocomplete_controller.cc
index c298c4310..65b4882 100644
--- a/components/omnibox/browser/autocomplete_controller.cc
+++ b/components/omnibox/browser/autocomplete_controller.cc
@@ -1425,6 +1425,13 @@
   stop_timer_duration_ = duration;
 }
 
+size_t AutocompleteController::InjectAdHocMatch(AutocompleteMatch match) {
+  size_t index = result_.size();
+  result_.AppendMatches({std::move(match)}, true);
+  NotifyChanged();
+  return index;
+}
+
 bool AutocompleteController::ShouldRunProvider(
     AutocompleteProvider* provider) const {
   if (OmniboxFieldTrial::IsSiteSearchStarterPackEnabled() &&
diff --git a/components/omnibox/browser/autocomplete_controller.h b/components/omnibox/browser/autocomplete_controller.h
index 9a80064..7c0702b 100644
--- a/components/omnibox/browser/autocomplete_controller.h
+++ b/components/omnibox/browser/autocomplete_controller.h
@@ -226,6 +226,10 @@
     return provider_client_.get();
   }
 
+  // This is a deprecated method of injecting an externally sourced
+  // match into the result set, currently still needed only by iOS.
+  size_t InjectAdHocMatch(AutocompleteMatch match);
+
  private:
   friend class FakeAutocompleteController;
   friend class AutocompleteProviderTest;
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index c521319..3a37877 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -251,7 +251,8 @@
   }
 }
 
-void AutocompleteResult::AppendMatches(const ACMatches& matches) {
+void AutocompleteResult::AppendMatches(const ACMatches& matches,
+                                       bool preserve) {
   for (const auto& match : matches) {
     DCHECK_EQ(AutocompleteMatch::SanitizeString(match.contents), match.contents)
         << "description: " << match.description
@@ -260,7 +261,7 @@
               match.description)
         << "contents: " << match.contents << ", match type: " << match.type;
     matches_.push_back(match);
-    if (!match.description.empty() &&
+    if (!preserve && !match.description.empty() &&
         !AutocompleteMatch::IsSearchType(match.type) &&
         match.type != ACMatchType::DOCUMENT_SUGGESTION) {
       matches_.back().swap_contents_and_description = true;
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index b3dae4b..9cbeefe 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -96,7 +96,8 @@
                           AutocompleteResult* old_matches);
 
   // Adds a new set of matches to the result set.  Does not re-sort.
-  void AppendMatches(const ACMatches& matches);
+  // When `preserve` is true, the matches are appended without modifications.
+  void AppendMatches(const ACMatches& matches, bool preserve = false);
 
   // Removes duplicates, puts the list in sorted order and culls to leave only
   // the best GetMaxMatches() matches. Sets the default match to the best match
diff --git a/components/omnibox/browser/autocomplete_scoring_model_handler.cc b/components/omnibox/browser/autocomplete_scoring_model_handler.cc
index 8ded5cd..20dc623 100644
--- a/components/omnibox/browser/autocomplete_scoring_model_handler.cc
+++ b/components/omnibox/browser/autocomplete_scoring_model_handler.cc
@@ -77,10 +77,7 @@
 AutocompleteScoringModelHandler::GetModelInput(
     const metrics::OmniboxEventProto::Suggestion::ScoringSignals&
         scoring_signals) {
-  if (!ModelAvailable()) {
-    return absl::nullopt;
-  }
-
+  DCHECK(ModelAvailable());
   absl::optional<AutocompleteScoringModelMetadata> model_metadata =
       ParsedSupportedFeaturesForLoadedModel<AutocompleteScoringModelMetadata>();
   if (!model_metadata) {
diff --git a/components/omnibox/browser/autocomplete_scoring_model_service.cc b/components/omnibox/browser/autocomplete_scoring_model_service.cc
index 5451337..ece9a1e 100644
--- a/components/omnibox/browser/autocomplete_scoring_model_service.cc
+++ b/components/omnibox/browser/autocomplete_scoring_model_service.cc
@@ -59,7 +59,8 @@
 }
 
 bool AutocompleteScoringModelService::UrlScoringModelAvailable() {
-  return url_scoring_model_handler_ != nullptr;
+  return url_scoring_model_handler_ &&
+         url_scoring_model_handler_->ModelAvailable();
 }
 
 void AutocompleteScoringModelService::ProcessModelOutput(
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 1ebea45b..92dd38d 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -785,93 +785,6 @@
             OmniboxPopupSelection::kNoMatch, match_selection_timestamp);
 }
 
-void OmniboxEditModel::AcceptInput(WindowOpenDisposition disposition,
-                                   base::TimeTicks match_selection_timestamp) {
-  // Get the URL and transition type for the selected entry.
-  GURL alternate_nav_url;
-  AutocompleteMatch match = CurrentMatch(&alternate_nav_url);
-
-  // If CTRL is down it means the user wants to append ".com" to the text they
-  // typed. If we can successfully generate a URL_WHAT_YOU_TYPED match doing
-  // that, then we use this. These matches are marked as generated by the
-  // HistoryURLProvider so we only generate them if this provider is present.
-  if (control_key_state_ == DOWN && !is_keyword_selected() &&
-      autocomplete_controller()->history_url_provider()) {
-    // For generating the hostname of the URL, we use the most recent
-    // input instead of the currently visible text. This means we'll ignore any
-    // visible inline autocompletion: if a user types "foo" and is autocompleted
-    // to "foodnetwork.com", ctrl-enter will navigate to "foo.com", not
-    // "foodnetwork.com".  At the time of writing, this behavior matches
-    // Internet Explorer, but not Firefox. Two exceptions to our own rule:
-    //  1. If the user has selected a suggestion, use the suggestion text.
-    //  2. If the user has never edited the text, use the current page's full
-    //     URL instead of the elided URL to avoid HTTPS downgrading.
-    std::u16string text_for_desired_tld_navigation = input_.text();
-    if (has_temporary_text_)
-      text_for_desired_tld_navigation = view_->GetText();
-    else if (!user_input_in_progress())
-      text_for_desired_tld_navigation = url_for_editing_;
-
-    // Generate a new AutocompleteInput, copying the latest one but using "com"
-    // as the desired TLD. Then use this autocomplete input to generate a
-    // URL_WHAT_YOU_TYPED AutocompleteMatch.
-    AutocompleteInput input(
-        text_for_desired_tld_navigation, input_.cursor_position(), "com",
-        input_.current_page_classification(), client_->GetSchemeClassifier(),
-        client_->ShouldDefaultTypedNavigationsToHttps(),
-        client_->GetHttpsPortForTesting(),
-        client_->IsUsingFakeHttpsForHttpsUpgradeTesting());
-    input.set_prevent_inline_autocomplete(input_.prevent_inline_autocomplete());
-    input.set_prefer_keyword(input_.prefer_keyword());
-    input.set_keyword_mode_entry_method(input_.keyword_mode_entry_method());
-    input.set_allow_exact_keyword_match(input_.allow_exact_keyword_match());
-    input.set_omit_asynchronous_matches(input_.omit_asynchronous_matches());
-    input.set_focus_type(input_.focus_type());
-    input_ = input;
-    AutocompleteMatch url_match(VerbatimMatchForInput(
-        autocomplete_controller()->history_url_provider(),
-        autocomplete_controller()->autocomplete_provider_client(), input_,
-        input_.canonicalized_url(), false));
-
-    if (url_match.destination_url.is_valid()) {
-      // We have a valid URL, we use this newly generated AutocompleteMatch.
-      match = url_match;
-      alternate_nav_url = GURL();
-    }
-  }
-
-  if (!match.destination_url.is_valid())
-    return;
-
-  if (ui::PageTransitionCoreTypeIs(match.transition,
-                                   ui::PAGE_TRANSITION_TYPED) &&
-      (match.destination_url == delegate()->GetLocationBarModel()->GetURL())) {
-    // When the user hit enter on the existing permanent URL, treat it like a
-    // reload for scoring purposes.  We could detect this by just checking
-    // user_input_in_progress_, but it seems better to treat "edits" that end
-    // up leaving the URL unchanged (e.g. deleting the last character and then
-    // retyping it) as reloads too.  We exclude non-TYPED transitions because if
-    // the transition is GENERATED, the user input something that looked
-    // different from the current URL, even if it wound up at the same place
-    // (e.g. manually retyping the same search query), and it seems wrong to
-    // treat this as a reload.
-    match.transition = ui::PAGE_TRANSITION_RELOAD;
-  } else if (paste_state_ != NONE &&
-             match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED) {
-    // When the user pasted in a URL and hit enter, score it like a link click
-    // rather than a normal typed URL, so it doesn't get inline autocompleted
-    // as aggressively later.
-    match.transition = ui::PAGE_TRANSITION_LINK;
-  }
-
-  client_->OnInputAccepted(match);
-
-  if (popup_view_) {
-    OpenMatch(match, disposition, alternate_nav_url, std::u16string(),
-              GetPopupSelection().line, match_selection_timestamp);
-  }
-}
-
 void OmniboxEditModel::EnterKeywordModeForDefaultSearchProvider(
     OmniboxEventProto::KeywordModeEntryMethod entry_method) {
   if (!client_->IsDefaultSearchProviderEnabled())
@@ -902,286 +815,6 @@
   EmitEnteredKeywordModeHistogram(entry_method, default_search_provider);
 }
 
-void OmniboxEditModel::OpenMatch(AutocompleteMatch match,
-                                 WindowOpenDisposition disposition,
-                                 const GURL& alternate_nav_url,
-                                 const std::u16string& pasted_text,
-                                 size_t index,
-                                 base::TimeTicks match_selection_timestamp) {
-  // If the match has an action that takes over the match,
-  // execute the action instead of opening the match.
-  if (ExecuteTakeoverAction(index, disposition, match_selection_timestamp)) {
-    return;
-  }
-
-  // Invalid URLs such as chrome://history can end up here.
-  if (!match.destination_url.is_valid()) {
-    return;
-  }
-
-  // NULL_RESULT_MESSAGE matches are informational only and cannot be acted
-  // upon. Immediately return when attempting to open one.
-  if (match.type == AutocompleteMatchType::NULL_RESULT_MESSAGE) {
-    return;
-  }
-
-  // TODO(manukh): Remove this histogram when `kRedoCurrentMatch` &
-  //   `kRevertModelBeforeClosingPopup` launch or are abandoned.
-  SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Omnibox.OpenMatchTime");
-
-  // Switch the window disposition to SWITCH_TO_TAB for open tab matches that
-  // originated while in keyword mode.  This is to support the keyword mode
-  // starter pack's tab search (@tabs) feature, which should open all
-  // suggestions in the existing open tab.
-  bool is_open_tab_match =
-      OmniboxFieldTrial::IsSiteSearchStarterPackEnabled() &&
-      match.from_keyword &&
-      match.provider->type() == AutocompleteProvider::TYPE_OPEN_TAB;
-  if (is_open_tab_match) {
-    disposition = WindowOpenDisposition::SWITCH_TO_TAB;
-  }
-
-  TRACE_EVENT("omnibox", "OmniboxEditModel::OpenMatch", "match", match,
-              "disposition", disposition, "altenate_nav_url", alternate_nav_url,
-              "pasted_text", pasted_text);
-  const base::TimeTicks& now(base::TimeTicks::Now());
-  base::TimeDelta elapsed_time_since_user_first_modified_omnibox(
-      now - time_user_first_modified_omnibox_);
-  autocomplete_controller()
-      ->UpdateMatchDestinationURLWithAdditionalAssistedQueryStats(
-          elapsed_time_since_user_first_modified_omnibox, &match);
-
-  // Save the result of the interaction, but do not record the histogram yet.
-  focus_resulted_in_navigation_ = true;
-
-  RecordActionShownForAllActions(result());
-  HistoryFuzzyProvider::RecordOpenMatchMetrics(result(), match);
-
-  std::u16string input_text(pasted_text);
-  if (input_text.empty())
-    input_text = user_input_in_progress_ ? user_text_ : url_for_editing_;
-  // Create a dummy AutocompleteInput for use in calling VerbatimMatchForInput()
-  // to create an alternate navigational match.
-  AutocompleteInput alternate_input(
-      input_text, GetPageClassification(), client_->GetSchemeClassifier(),
-      client_->ShouldDefaultTypedNavigationsToHttps(),
-      client_->GetHttpsPortForTesting(),
-      client_->IsUsingFakeHttpsForHttpsUpgradeTesting());
-  // Somehow we can occasionally get here with no active tab.  It's not
-  // clear why this happens.
-  alternate_input.set_current_url(client_->GetURL());
-  alternate_input.set_current_title(client_->GetTitle());
-
-  base::TimeDelta elapsed_time_since_last_change_to_default_match(
-      now - autocomplete_controller()->last_time_default_match_changed());
-  DCHECK(match.provider);
-  // These elapsed times don't really make sense for matches that come from
-  // omnibox focus (because the user did not modify the omnibox), so for those
-  // we set the elapsed times to something that will be ignored by
-  // metrics_log.cc.  They also don't necessarily make sense if the omnibox
-  // dropdown is closed or the user used a paste-and-go action.  (In most
-  // cases when this happens, the user never modified the omnibox.)
-  const bool popup_open = PopupIsOpen();
-  if (input_.focus_type() != metrics::OmniboxFocusType::INTERACTION_DEFAULT ||
-      !popup_open || !pasted_text.empty()) {
-    const base::TimeDelta default_time_delta = base::Milliseconds(-1);
-    elapsed_time_since_user_first_modified_omnibox = default_time_delta;
-    elapsed_time_since_last_change_to_default_match = default_time_delta;
-  }
-
-  // In some unusual cases, we ignore result() and instead log a fake result set
-  // with a single element (|match|) and selected_index of 0. For these cases:
-  //  1. If the popup is closed (there is no result set).
-  //  2. If the index is out of bounds. This should only happen if |index| is
-  //     kNoMatch, which can happen if the default search provider is disabled.
-  //  3. If this is a paste-and-go action (meaning the contents of the dropdown
-  //     are ignored regardless).
-  const bool dropdown_ignored =
-      !popup_open || index >= result().size() || !pasted_text.empty();
-  ACMatches fake_single_entry_matches;
-  fake_single_entry_matches.push_back(match);
-  AutocompleteResult fake_single_entry_result;
-  fake_single_entry_result.AppendMatches(fake_single_entry_matches);
-
-  const std::u16string& user_text =
-      input_.focus_type() != metrics::OmniboxFocusType::INTERACTION_DEFAULT
-          ? std::u16string()
-          : input_text;
-  size_t completed_length = match.allowed_to_be_default_match
-                                ? match.inline_autocompletion.length()
-                                : std::u16string::npos;
-  bool is_incognito = autocomplete_controller()
-                          ->autocomplete_provider_client()
-                          ->IsOffTheRecord();
-  OmniboxLog log(
-      user_text, just_deleted_text_, input_.type(), is_keyword_selected(),
-      keyword_mode_entry_method_, popup_open, dropdown_ignored ? 0 : index,
-      disposition, !pasted_text.empty(),
-      SessionID::InvalidValue(),  // don't know tab ID; set later if appropriate
-      GetPageClassification(), elapsed_time_since_user_first_modified_omnibox,
-      completed_length, elapsed_time_since_last_change_to_default_match,
-      dropdown_ignored ? fake_single_entry_result : result(),
-      match.destination_url, is_incognito);
-  DCHECK(dropdown_ignored ||
-         (log.elapsed_time_since_user_first_modified_omnibox >=
-          log.elapsed_time_since_last_change_to_default_match))
-      << "We should've got the notification that the user modified the "
-      << "omnibox text at same time or before the most recent time the "
-      << "default match changed.";
-
-  if ((disposition == WindowOpenDisposition::CURRENT_TAB) &&
-      client_->CurrentPageExists()) {
-    // If we know the destination is being opened in the current tab,
-    // we can easily get the tab ID.  (If it's being opened in a new
-    // tab, we don't know the tab ID yet.)
-    log.tab_id = client_->GetSessionID();
-  }
-  autocomplete_controller()->AddProviderAndTriggeringLogs(&log);
-
-  base::UmaHistogramEnumeration("Omnibox.SuggestionUsed.RichAutocompletion",
-                                match.rich_autocompletion_triggered);
-  size_t ipv4_parts_count = CountNumberOfIPv4Parts(
-      user_text, match.destination_url, completed_length);
-  // The histogram is collected to decide if shortened IPv4 addresses
-  // like 127.1 should be deprecated.
-  // Only valid IP addresses manually inputted by the user will be counted.
-  if (ipv4_parts_count > 0) {
-    base::UmaHistogramCounts100("Omnibox.IPv4AddressPartsCount",
-                                ipv4_parts_count);
-  }
-
-  client_->OnURLOpenedFromOmnibox(&log);
-  OmniboxEventGlobalTracker::GetInstance()->OnURLOpened(&log);
-
-  LOCAL_HISTOGRAM_BOOLEAN("Omnibox.EventCount", true);
-  SuggestionAnswer::LogAnswerUsed(match.answer);
-  if (!last_omnibox_focus_.is_null()) {
-    // Only record focus to open time when a focus actually happened (as
-    // opposed to, say, dragging a link onto the omnibox).
-    UMA_HISTOGRAM_MEDIUM_TIMES(kFocusToOpenTimeHistogram,
-                               now - last_omnibox_focus_);
-  }
-
-  IDNA2008DeviationCharacter deviation_char_in_hostname =
-      IDNA2008DeviationCharacter::kNone;
-
-  TemplateURLService* service = client_->GetTemplateURLService();
-  TemplateURL* template_url = match.GetTemplateURL(service, false);
-  if (template_url) {
-    if (ui::PageTransitionTypeIncludingQualifiersIs(
-            match.transition, ui::PAGE_TRANSITION_KEYWORD)) {
-      // The user is using a non-substituting keyword or is explicitly in
-      // keyword mode.
-
-      // Don't increment usage count for extension keywords.
-      if (client_->ProcessExtensionKeyword(input_text, template_url, match,
-                                           disposition)) {
-        if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB)
-          view_->RevertAll();
-        return;
-      }
-
-      base::RecordAction(base::UserMetricsAction("AcceptedKeyword"));
-      EmitAcceptedKeywordSuggestionHistogram(keyword_mode_entry_method_,
-                                             template_url);
-      client_->GetTemplateURLService()->IncrementUsageCount(template_url);
-    } else {
-      DCHECK(ui::PageTransitionTypeIncludingQualifiersIs(
-                 match.transition, ui::PAGE_TRANSITION_GENERATED) ||
-             ui::PageTransitionTypeIncludingQualifiersIs(
-                 match.transition, ui::PAGE_TRANSITION_RELOAD));
-      // NOTE: We purposefully don't increment the usage count of the default
-      // search engine here like we do for explicit keywords above; see comments
-      // in template_url.h.
-    }
-
-    AutocompleteMatch::LogSearchEngineUsed(match, service);
-  } else {
-    // |match| is a URL navigation, not a search.
-    // For logging the below histogram, only record uses that depend on the
-    // omnibox suggestion system, i.e., TYPED navigations.  That is, exclude
-    // omnibox URL interactions that are treated as reloads or link-following
-    // (i.e., cut-and-paste of URLs) or paste-and-go.
-    if (ui::PageTransitionTypeIncludingQualifiersIs(
-            match.transition, ui::PAGE_TRANSITION_TYPED) &&
-        pasted_text.empty()) {
-      navigation_metrics::RecordOmniboxURLNavigation(match.destination_url);
-    }
-
-    // The following histograms should be recorded for both TYPED and pasted
-    // URLs, but should still exclude reloads.
-    if (ui::PageTransitionTypeIncludingQualifiersIs(
-            match.transition, ui::PAGE_TRANSITION_TYPED) ||
-        ui::PageTransitionTypeIncludingQualifiersIs(match.transition,
-                                                    ui::PAGE_TRANSITION_LINK)) {
-      net::cookie_util::RecordCookiePortOmniboxHistograms(
-          match.destination_url);
-
-      if (match.destination_url.SchemeIsHTTPOrHTTPS()) {
-        // Extract the typed hostname from autocomplete input for IDNA 2008
-        // metrics. We can't use GURL here as it removes the deviation
-        // characters that we want to measure.
-        size_t hostname_begin = input_.parts().host.begin;
-        if (input_.added_default_scheme_to_typed_url() && hostname_begin > 0) {
-          // If the omnibox upgrades a navigation to https, it offsets
-          // components by one to the right due to the added "s" to http. Adjust
-          // the offset again. Ideally, hostname_begin should always be non-zero
-          // in that case, but we check it for safety.
-          --hostname_begin;
-        }
-        std::u16string hostname(input_.text(), hostname_begin,
-                                static_cast<size_t>(input_.parts().host.len));
-        deviation_char_in_hostname =
-            navigation_metrics::RecordIDNA2008Metrics(hostname);
-      }
-    }
-  }
-
-  if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB) {
-    base::AutoReset<bool> tmp(&in_revert_, true);
-    view_->RevertAll();  // Revert the box to its unedited state.
-  }
-
-  // Track whether the destination URL sends us to a search results page
-  // using the default search provider.
-  TemplateURLService* template_url_service = client_->GetTemplateURLService();
-  if (template_url_service &&
-      template_url_service->IsSearchResultsPageFromDefaultSearchProvider(
-          match.destination_url)) {
-    base::RecordAction(
-        base::UserMetricsAction("OmniboxDestinationURLIsSearchOnDSP"));
-
-    // Re-use the result of the incognito check above in the experiment arm,
-    // otherwise re-compute the off the record state.
-    bool is_off_the_record =
-        use_existing_autocomplete_client_
-            ? is_incognito
-            : client_->CreateAutocompleteProviderClient()->IsOffTheRecord();
-    base::UmaHistogramBoolean("Omnibox.Search.OffTheRecord", is_off_the_record);
-  }
-
-  if (match.destination_url.is_valid()) {
-    // This calls RevertAll again.
-    base::AutoReset<bool> tmp(&in_revert_, true);
-
-    edit_model_delegate_->OnAutocompleteAccept(
-        match.destination_url, match.post_content.get(), disposition,
-        ui::PageTransitionFromInt(match.transition |
-                                  ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
-        match.type, match_selection_timestamp,
-        input_.added_default_scheme_to_typed_url(), input_text, match,
-        VerbatimMatchForInput(
-            autocomplete_controller()->history_url_provider(),
-            autocomplete_controller()->autocomplete_provider_client(),
-            alternate_input, alternate_nav_url, false),
-        deviation_char_in_hostname);
-  }
-
-  BookmarkModel* bookmark_model = client_->GetBookmarkModel();
-  if (bookmark_model && bookmark_model->IsBookmarked(match.destination_url))
-    client_->OnBookmarkLaunched();
-}
-
 void OmniboxEditModel::OpenSelection(OmniboxPopupSelection selection,
                                      base::TimeTicks timestamp,
                                      WindowOpenDisposition disposition) {
@@ -1202,15 +835,6 @@
 
   const AutocompleteMatch& match = result().match_at(selection.line);
 
-  // TODO(crbug/1408506): This is an exceptional quirk for the new
-  //  match takeover actions mechanism, and can likely be simplified.
-  const OmniboxAction* action = match.GetPrimaryAction();
-  if (selection.state == OmniboxPopupSelection::NORMAL &&
-      (action == nullptr || !action->TakesOverMatch())) {
-    AcceptInput(disposition, timestamp);
-    return;
-  }
-
   if (selection.state == OmniboxPopupSelection::FOCUSED_BUTTON_HEADER) {
     DCHECK(match.suggestion_group_id.has_value());
 
@@ -1223,25 +847,20 @@
              OmniboxPopupSelection::FOCUSED_BUTTON_REMOVE_SUGGESTION) {
     TryDeletingPopupLine(selection.line);
   } else if (selection.state == OmniboxPopupSelection::FOCUSED_BUTTON_ACTION) {
-    // TODO(crbug/1408506): Either handle in OpenMatch or convert OpenMatch
-    //  usage to OpenSelection so this can keep a consistent code
-    //  and metrics flow. The broad goal is to eliminate one or the other,
-    //  or at least hide one as private so call sites are all consistent.
     DCHECK(timestamp != base::TimeTicks());
     ExecuteAction(selection, disposition, timestamp);
   } else {
-    DCHECK(timestamp != base::TimeTicks());
     if (selection.state == OmniboxPopupSelection::FOCUSED_BUTTON_TAB_SWITCH) {
       disposition = WindowOpenDisposition::SWITCH_TO_TAB;
     }
-    OpenMatch(match, disposition, GURL(), std::u16string(),
-              GetPopupSelection().line, timestamp);
+    OpenMatch(match, disposition, GURL(), std::u16string(), selection.line,
+              timestamp);
   }
 }
 
 void OmniboxEditModel::OpenSelection(base::TimeTicks timestamp,
                                      WindowOpenDisposition disposition) {
-  OpenSelection(GetPopupSelection(), timestamp, disposition);
+  OpenSelection(popup_selection_, timestamp, disposition);
 }
 
 bool OmniboxEditModel::InExplicitExperimentalKeywordMode() {
@@ -1649,6 +1268,17 @@
                               navigation_predictor);
 }
 
+void OmniboxEditModel::OpenMatchForTesting(
+    AutocompleteMatch match,
+    WindowOpenDisposition disposition,
+    const GURL& alternate_nav_url,
+    const std::u16string& pasted_text,
+    size_t index,
+    base::TimeTicks match_selection_timestamp) {
+  OpenMatch(match, disposition, alternate_nav_url, pasted_text, index,
+            match_selection_timestamp);
+}
+
 bool OmniboxEditModel::MaybeStartQueryForPopup() {
   if (PopupIsOpen()) {
     return false;
@@ -2446,6 +2076,93 @@
   return autocomplete_controller()->autocomplete_provider_client()->GetPrefs();
 }
 
+void OmniboxEditModel::AcceptInput(WindowOpenDisposition disposition,
+                                   base::TimeTicks match_selection_timestamp) {
+  // Get the URL and transition type for the selected entry.
+  GURL alternate_nav_url;
+  AutocompleteMatch match = CurrentMatch(&alternate_nav_url);
+
+  // If CTRL is down it means the user wants to append ".com" to the text they
+  // typed. If we can successfully generate a URL_WHAT_YOU_TYPED match doing
+  // that, then we use this. These matches are marked as generated by the
+  // HistoryURLProvider so we only generate them if this provider is present.
+  if (control_key_state_ == DOWN && !is_keyword_selected() &&
+      autocomplete_controller()->history_url_provider()) {
+    // For generating the hostname of the URL, we use the most recent
+    // input instead of the currently visible text. This means we'll ignore any
+    // visible inline autocompletion: if a user types "foo" and is autocompleted
+    // to "foodnetwork.com", ctrl-enter will navigate to "foo.com", not
+    // "foodnetwork.com".  At the time of writing, this behavior matches
+    // Internet Explorer, but not Firefox. Two exceptions to our own rule:
+    //  1. If the user has selected a suggestion, use the suggestion text.
+    //  2. If the user has never edited the text, use the current page's full
+    //     URL instead of the elided URL to avoid HTTPS downgrading.
+    std::u16string text_for_desired_tld_navigation = input_.text();
+    if (has_temporary_text_) {
+      text_for_desired_tld_navigation = view_->GetText();
+    } else if (!user_input_in_progress()) {
+      text_for_desired_tld_navigation = url_for_editing_;
+    }
+
+    // Generate a new AutocompleteInput, copying the latest one but using "com"
+    // as the desired TLD. Then use this autocomplete input to generate a
+    // URL_WHAT_YOU_TYPED AutocompleteMatch.
+    AutocompleteInput input(
+        text_for_desired_tld_navigation, input_.cursor_position(), "com",
+        input_.current_page_classification(), client_->GetSchemeClassifier(),
+        client_->ShouldDefaultTypedNavigationsToHttps(), 0, false);
+    input.set_prevent_inline_autocomplete(input_.prevent_inline_autocomplete());
+    input.set_prefer_keyword(input_.prefer_keyword());
+    input.set_keyword_mode_entry_method(input_.keyword_mode_entry_method());
+    input.set_allow_exact_keyword_match(input_.allow_exact_keyword_match());
+    input.set_omit_asynchronous_matches(input_.omit_asynchronous_matches());
+    input.set_focus_type(input_.focus_type());
+    input_ = input;
+    AutocompleteMatch url_match(VerbatimMatchForInput(
+        autocomplete_controller()->history_url_provider(),
+        autocomplete_controller()->autocomplete_provider_client(), input_,
+        input_.canonicalized_url(), false));
+
+    if (url_match.destination_url.is_valid()) {
+      // We have a valid URL, we use this newly generated AutocompleteMatch.
+      match = url_match;
+      alternate_nav_url = GURL();
+    }
+  }
+
+  if (!match.destination_url.is_valid()) {
+    return;
+  }
+
+  if (ui::PageTransitionCoreTypeIs(match.transition,
+                                   ui::PAGE_TRANSITION_TYPED) &&
+      (match.destination_url == delegate()->GetLocationBarModel()->GetURL())) {
+    // When the user hit enter on the existing permanent URL, treat it like a
+    // reload for scoring purposes.  We could detect this by just checking
+    // user_input_in_progress_, but it seems better to treat "edits" that end
+    // up leaving the URL unchanged (e.g. deleting the last character and then
+    // retyping it) as reloads too.  We exclude non-TYPED transitions because if
+    // the transition is GENERATED, the user input something that looked
+    // different from the current URL, even if it wound up at the same place
+    // (e.g. manually retyping the same search query), and it seems wrong to
+    // treat this as a reload.
+    match.transition = ui::PAGE_TRANSITION_RELOAD;
+  } else if (paste_state_ != NONE &&
+             match.type == AutocompleteMatchType::URL_WHAT_YOU_TYPED) {
+    // When the user pasted in a URL and hit enter, score it like a link click
+    // rather than a normal typed URL, so it doesn't get inline autocompleted
+    // as aggressively later.
+    match.transition = ui::PAGE_TRANSITION_LINK;
+  }
+
+  client_->OnInputAccepted(match);
+
+  if (popup_view_) {
+    OpenMatch(match, disposition, alternate_nav_url, std::u16string(),
+              GetPopupSelection().line, match_selection_timestamp);
+  }
+}
+
 bool OmniboxEditModel::ExecuteTakeoverAction(
     size_t match_index,
     WindowOpenDisposition disposition,
@@ -2492,6 +2209,287 @@
   }
 }
 
+void OmniboxEditModel::OpenMatch(AutocompleteMatch match,
+                                 WindowOpenDisposition disposition,
+                                 const GURL& alternate_nav_url,
+                                 const std::u16string& pasted_text,
+                                 size_t index,
+                                 base::TimeTicks match_selection_timestamp) {
+  // If the match has an action that takes over the match,
+  // execute the action instead of opening the match.
+  if (ExecuteTakeoverAction(index, disposition, match_selection_timestamp)) {
+    return;
+  }
+
+  // Invalid URLs such as chrome://history can end up here.
+  if (!match.destination_url.is_valid()) {
+    return;
+  }
+
+  // NULL_RESULT_MESSAGE matches are informational only and cannot be acted
+  // upon. Immediately return when attempting to open one.
+  if (match.type == AutocompleteMatchType::NULL_RESULT_MESSAGE) {
+    return;
+  }
+
+  // TODO(manukh): Remove this histogram when `kRedoCurrentMatch` &
+  //   `kRevertModelBeforeClosingPopup` launch or are abandoned.
+  SCOPED_UMA_HISTOGRAM_TIMER_MICROS("Omnibox.OpenMatchTime");
+
+  // Switch the window disposition to SWITCH_TO_TAB for open tab matches that
+  // originated while in keyword mode.  This is to support the keyword mode
+  // starter pack's tab search (@tabs) feature, which should open all
+  // suggestions in the existing open tab.
+  bool is_open_tab_match =
+      OmniboxFieldTrial::IsSiteSearchStarterPackEnabled() &&
+      match.from_keyword &&
+      match.provider->type() == AutocompleteProvider::TYPE_OPEN_TAB;
+  if (is_open_tab_match) {
+    disposition = WindowOpenDisposition::SWITCH_TO_TAB;
+  }
+
+  TRACE_EVENT("omnibox", "OmniboxEditModel::OpenMatch", "match", match,
+              "disposition", disposition, "altenate_nav_url", alternate_nav_url,
+              "pasted_text", pasted_text);
+  const base::TimeTicks& now(base::TimeTicks::Now());
+  base::TimeDelta elapsed_time_since_user_first_modified_omnibox(
+      now - time_user_first_modified_omnibox_);
+  autocomplete_controller()
+      ->UpdateMatchDestinationURLWithAdditionalAssistedQueryStats(
+          elapsed_time_since_user_first_modified_omnibox, &match);
+
+  // Save the result of the interaction, but do not record the histogram yet.
+  focus_resulted_in_navigation_ = true;
+
+  RecordActionShownForAllActions(result());
+  HistoryFuzzyProvider::RecordOpenMatchMetrics(result(), match);
+
+  std::u16string input_text(pasted_text);
+  if (input_text.empty()) {
+    input_text = user_input_in_progress_ ? user_text_ : url_for_editing_;
+  }
+  // Create a dummy AutocompleteInput for use in calling VerbatimMatchForInput()
+  // to create an alternate navigational match.
+  AutocompleteInput alternate_input(
+      input_text, GetPageClassification(), client_->GetSchemeClassifier(),
+      client_->ShouldDefaultTypedNavigationsToHttps(), 0, false);
+  // Somehow we can occasionally get here with no active tab.  It's not
+  // clear why this happens.
+  alternate_input.set_current_url(client_->GetURL());
+  alternate_input.set_current_title(client_->GetTitle());
+
+  base::TimeDelta elapsed_time_since_last_change_to_default_match(
+      now - autocomplete_controller()->last_time_default_match_changed());
+  DCHECK(match.provider);
+  // These elapsed times don't really make sense for matches that come from
+  // omnibox focus (because the user did not modify the omnibox), so for those
+  // we set the elapsed times to something that will be ignored by
+  // metrics_log.cc.  They also don't necessarily make sense if the omnibox
+  // dropdown is closed or the user used a paste-and-go action.  (In most
+  // cases when this happens, the user never modified the omnibox.)
+  const bool popup_open = PopupIsOpen();
+  if (input_.focus_type() != metrics::OmniboxFocusType::INTERACTION_DEFAULT ||
+      !popup_open || !pasted_text.empty()) {
+    const base::TimeDelta default_time_delta = base::Milliseconds(-1);
+    elapsed_time_since_user_first_modified_omnibox = default_time_delta;
+    elapsed_time_since_last_change_to_default_match = default_time_delta;
+  }
+
+  // In some unusual cases, we ignore result() and instead log a fake result set
+  // with a single element (|match|) and selected_index of 0. For these cases:
+  //  1. If the popup is closed (there is no result set).
+  //  2. If the index is out of bounds. This should only happen if |index| is
+  //     kNoMatch, which can happen if the default search provider is disabled.
+  //  3. If this is a paste-and-go action (meaning the contents of the dropdown
+  //     are ignored regardless).
+  const bool dropdown_ignored =
+      !popup_open || index >= result().size() || !pasted_text.empty();
+  ACMatches fake_single_entry_matches;
+  fake_single_entry_matches.push_back(match);
+  AutocompleteResult fake_single_entry_result;
+  fake_single_entry_result.AppendMatches(fake_single_entry_matches);
+
+  const std::u16string& user_text =
+      input_.focus_type() != metrics::OmniboxFocusType::INTERACTION_DEFAULT
+          ? std::u16string()
+          : input_text;
+  size_t completed_length = match.allowed_to_be_default_match
+                                ? match.inline_autocompletion.length()
+                                : std::u16string::npos;
+  bool is_incognito = autocomplete_controller()
+                          ->autocomplete_provider_client()
+                          ->IsOffTheRecord();
+  OmniboxLog log(
+      user_text, just_deleted_text_, input_.type(), is_keyword_selected(),
+      keyword_mode_entry_method_, popup_open, dropdown_ignored ? 0 : index,
+      disposition, !pasted_text.empty(),
+      SessionID::InvalidValue(),  // don't know tab ID; set later if appropriate
+      GetPageClassification(), elapsed_time_since_user_first_modified_omnibox,
+      completed_length, elapsed_time_since_last_change_to_default_match,
+      dropdown_ignored ? fake_single_entry_result : result(),
+      match.destination_url, is_incognito);
+  DCHECK(dropdown_ignored ||
+         (log.elapsed_time_since_user_first_modified_omnibox >=
+          log.elapsed_time_since_last_change_to_default_match))
+      << "We should've got the notification that the user modified the "
+      << "omnibox text at same time or before the most recent time the "
+      << "default match changed.";
+
+  if ((disposition == WindowOpenDisposition::CURRENT_TAB) &&
+      client_->CurrentPageExists()) {
+    // If we know the destination is being opened in the current tab,
+    // we can easily get the tab ID.  (If it's being opened in a new
+    // tab, we don't know the tab ID yet.)
+    log.tab_id = client_->GetSessionID();
+  }
+  autocomplete_controller()->AddProviderAndTriggeringLogs(&log);
+
+  base::UmaHistogramEnumeration("Omnibox.SuggestionUsed.RichAutocompletion",
+                                match.rich_autocompletion_triggered);
+  size_t ipv4_parts_count = CountNumberOfIPv4Parts(
+      user_text, match.destination_url, completed_length);
+  // The histogram is collected to decide if shortened IPv4 addresses
+  // like 127.1 should be deprecated.
+  // Only valid IP addresses manually inputted by the user will be counted.
+  if (ipv4_parts_count > 0) {
+    base::UmaHistogramCounts100("Omnibox.IPv4AddressPartsCount",
+                                ipv4_parts_count);
+  }
+
+  client_->OnURLOpenedFromOmnibox(&log);
+  OmniboxEventGlobalTracker::GetInstance()->OnURLOpened(&log);
+
+  LOCAL_HISTOGRAM_BOOLEAN("Omnibox.EventCount", true);
+  SuggestionAnswer::LogAnswerUsed(match.answer);
+  if (!last_omnibox_focus_.is_null()) {
+    // Only record focus to open time when a focus actually happened (as
+    // opposed to, say, dragging a link onto the omnibox).
+    UMA_HISTOGRAM_MEDIUM_TIMES(kFocusToOpenTimeHistogram,
+                               now - last_omnibox_focus_);
+  }
+
+  IDNA2008DeviationCharacter deviation_char_in_hostname =
+      IDNA2008DeviationCharacter::kNone;
+
+  TemplateURLService* service = client_->GetTemplateURLService();
+  TemplateURL* template_url = match.GetTemplateURL(service, false);
+  if (template_url) {
+    if (ui::PageTransitionTypeIncludingQualifiersIs(
+            match.transition, ui::PAGE_TRANSITION_KEYWORD)) {
+      // The user is using a non-substituting keyword or is explicitly in
+      // keyword mode.
+
+      // Don't increment usage count for extension keywords.
+      if (client_->ProcessExtensionKeyword(input_text, template_url, match,
+                                           disposition)) {
+        if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB) {
+          view_->RevertAll();
+        }
+        return;
+      }
+
+      base::RecordAction(base::UserMetricsAction("AcceptedKeyword"));
+      EmitAcceptedKeywordSuggestionHistogram(keyword_mode_entry_method_,
+                                             template_url);
+      client_->GetTemplateURLService()->IncrementUsageCount(template_url);
+    } else {
+      DCHECK(ui::PageTransitionTypeIncludingQualifiersIs(
+                 match.transition, ui::PAGE_TRANSITION_GENERATED) ||
+             ui::PageTransitionTypeIncludingQualifiersIs(
+                 match.transition, ui::PAGE_TRANSITION_RELOAD));
+      // NOTE: We purposefully don't increment the usage count of the default
+      // search engine here like we do for explicit keywords above; see comments
+      // in template_url.h.
+    }
+
+    AutocompleteMatch::LogSearchEngineUsed(match, service);
+  } else {
+    // |match| is a URL navigation, not a search.
+    // For logging the below histogram, only record uses that depend on the
+    // omnibox suggestion system, i.e., TYPED navigations.  That is, exclude
+    // omnibox URL interactions that are treated as reloads or link-following
+    // (i.e., cut-and-paste of URLs) or paste-and-go.
+    if (ui::PageTransitionTypeIncludingQualifiersIs(
+            match.transition, ui::PAGE_TRANSITION_TYPED) &&
+        pasted_text.empty()) {
+      navigation_metrics::RecordOmniboxURLNavigation(match.destination_url);
+    }
+
+    // The following histograms should be recorded for both TYPED and pasted
+    // URLs, but should still exclude reloads.
+    if (ui::PageTransitionTypeIncludingQualifiersIs(
+            match.transition, ui::PAGE_TRANSITION_TYPED) ||
+        ui::PageTransitionTypeIncludingQualifiersIs(match.transition,
+                                                    ui::PAGE_TRANSITION_LINK)) {
+      net::cookie_util::RecordCookiePortOmniboxHistograms(
+          match.destination_url);
+
+      if (match.destination_url.SchemeIsHTTPOrHTTPS()) {
+        // Extract the typed hostname from autocomplete input for IDNA 2008
+        // metrics. We can't use GURL here as it removes the deviation
+        // characters that we want to measure.
+        size_t hostname_begin = input_.parts().host.begin;
+        if (input_.added_default_scheme_to_typed_url() && hostname_begin > 0) {
+          // If the omnibox upgrades a navigation to https, it offsets
+          // components by one to the right due to the added "s" to http. Adjust
+          // the offset again. Ideally, hostname_begin should always be non-zero
+          // in that case, but we check it for safety.
+          --hostname_begin;
+        }
+        std::u16string hostname(input_.text(), hostname_begin,
+                                static_cast<size_t>(input_.parts().host.len));
+        deviation_char_in_hostname =
+            navigation_metrics::RecordIDNA2008Metrics(hostname);
+      }
+    }
+  }
+
+  if (disposition != WindowOpenDisposition::NEW_BACKGROUND_TAB) {
+    base::AutoReset<bool> tmp(&in_revert_, true);
+    view_->RevertAll();  // Revert the box to its unedited state.
+  }
+
+  // Track whether the destination URL sends us to a search results page
+  // using the default search provider.
+  TemplateURLService* template_url_service = client_->GetTemplateURLService();
+  if (template_url_service &&
+      template_url_service->IsSearchResultsPageFromDefaultSearchProvider(
+          match.destination_url)) {
+    base::RecordAction(
+        base::UserMetricsAction("OmniboxDestinationURLIsSearchOnDSP"));
+
+    // Re-use the result of the incognito check above in the experiment arm,
+    // otherwise re-compute the off the record state.
+    bool is_off_the_record =
+        use_existing_autocomplete_client_
+            ? is_incognito
+            : client_->CreateAutocompleteProviderClient()->IsOffTheRecord();
+    base::UmaHistogramBoolean("Omnibox.Search.OffTheRecord", is_off_the_record);
+  }
+
+  if (match.destination_url.is_valid()) {
+    // This calls RevertAll again.
+    base::AutoReset<bool> tmp(&in_revert_, true);
+
+    edit_model_delegate_->OnAutocompleteAccept(
+        match.destination_url, match.post_content.get(), disposition,
+        ui::PageTransitionFromInt(match.transition |
+                                  ui::PAGE_TRANSITION_FROM_ADDRESS_BAR),
+        match.type, match_selection_timestamp,
+        input_.added_default_scheme_to_typed_url(), input_text, match,
+        VerbatimMatchForInput(
+            autocomplete_controller()->history_url_provider(),
+            autocomplete_controller()->autocomplete_provider_client(),
+            alternate_input, alternate_nav_url, false),
+        deviation_char_in_hostname);
+  }
+
+  BookmarkModel* bookmark_model = client_->GetBookmarkModel();
+  if (bookmark_model && bookmark_model->IsBookmarked(match.destination_url)) {
+    client_->OnBookmarkLaunched();
+  }
+}
+
 bool OmniboxEditModel::AllowKeywordSpaceTriggering() const {
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
   return GetPrefService()->GetBoolean(omnibox::kKeywordSpaceTriggeringEnabled);
diff --git a/components/omnibox/browser/omnibox_edit_model.h b/components/omnibox/browser/omnibox_edit_model.h
index a72f1a5..3f2c960 100644
--- a/components/omnibox/browser/omnibox_edit_model.h
+++ b/components/omnibox/browser/omnibox_edit_model.h
@@ -217,39 +217,6 @@
                       AutocompleteMatch* match,
                       GURL* alternate_nav_url) const;
 
-  // Asks the browser to load the popup's currently selected item, using the
-  // supplied disposition.  This may close the popup.
-  void AcceptInput(
-      WindowOpenDisposition disposition,
-      base::TimeTicks match_selection_timestamp = base::TimeTicks());
-
-  // Asks the browser to load |match|. |index| is only used for logging, and
-  // can be kNoMatch if the popup was closed, or if none of the suggestions
-  // in the popup were used (in the unusual no-default-match case). In that
-  // case, an artificial result set with only |match| will be logged.
-  //
-  // OpenMatch() needs to know the original text that drove this action.  If
-  // |pasted_text| is non-empty, this is a Paste-And-Go/Search action, and
-  // that's the relevant input text.  Otherwise, the relevant input text is
-  // either the user text or the display URL, depending on if user input is
-  // in progress.
-  //
-  // |match| is passed by value for two reasons:
-  // (1) This function needs to modify |match|, so a const ref isn't
-  //     appropriate.  Callers don't actually care about the modifications, so a
-  //     pointer isn't required.
-  // (2) The passed-in match is, on the caller side, typically coming from data
-  //     associated with the popup.  Since this call can close the popup, that
-  //     could clear that data, leaving us with a pointer-to-garbage.  So at
-  //     some point someone needs to make a copy of the match anyway, to
-  //     preserve it past the popup closure.
-  void OpenMatch(AutocompleteMatch match,
-                 WindowOpenDisposition disposition,
-                 const GURL& alternate_nav_url,
-                 const std::u16string& pasted_text,
-                 size_t index,
-                 base::TimeTicks match_selection_timestamp = base::TimeTicks());
-
   // Opens given selection. Most kinds of selection invoke an action or
   // otherwise call `OpenMatch`, but some may `AcceptInput` which is not
   // guaranteed to open a match or commit the omnibox.
@@ -512,6 +479,17 @@
       size_t line,
       omnibox::mojom::NavigationPredictor navigation_predictor);
 
+  // This calls `OpenMatch` directly for the few remaining `OmniboxEditModel`
+  // test cases that require explicit control over match content. For new
+  // tests, and for non-test code, use `OpenSelection`.
+  void OpenMatchForTesting(
+      AutocompleteMatch match,
+      WindowOpenDisposition disposition,
+      const GURL& alternate_nav_url,
+      const std::u16string& pasted_text,
+      size_t index,
+      base::TimeTicks match_selection_timestamp = base::TimeTicks());
+
  protected:
   // Utility method to get current PrefService; protected instead of private
   // because it may be overridden by derived test classes.
@@ -546,6 +524,12 @@
                        // with ctrl-l or copying the selected text with ctrl-c.
   };
 
+  // Asks the browser to load the popup's currently selected item, using the
+  // supplied disposition.  This may close the popup.
+  void AcceptInput(
+      WindowOpenDisposition disposition,
+      base::TimeTicks match_selection_timestamp = base::TimeTicks());
+
   // If the match in result() specified by `match_index` has an
   // action that takes over the match, this executes that action
   // with given `disposition` and returns true. Returns false otherwise.
@@ -560,6 +544,33 @@
                      WindowOpenDisposition disposition,
                      base::TimeTicks match_selection_timestamp);
 
+  // Asks the browser to load |match|. |index| is only used for logging, and
+  // can be kNoMatch if the popup was closed, or if none of the suggestions
+  // in the popup were used (in the unusual no-default-match case). In that
+  // case, an artificial result set with only |match| will be logged.
+  //
+  // OpenMatch() needs to know the original text that drove this action.  If
+  // |pasted_text| is non-empty, this is a Paste-And-Go/Search action, and
+  // that's the relevant input text.  Otherwise, the relevant input text is
+  // either the user text or the display URL, depending on if user input is
+  // in progress.
+  //
+  // |match| is passed by value for two reasons:
+  // (1) This function needs to modify |match|, so a const ref isn't
+  //     appropriate.  Callers don't actually care about the modifications, so a
+  //     pointer isn't required.
+  // (2) The passed-in match is, on the caller side, typically coming from data
+  //     associated with the popup.  Since this call can close the popup, that
+  //     could clear that data, leaving us with a pointer-to-garbage.  So at
+  //     some point someone needs to make a copy of the match anyway, to
+  //     preserve it past the popup closure.
+  void OpenMatch(AutocompleteMatch match,
+                 WindowOpenDisposition disposition,
+                 const GURL& alternate_nav_url,
+                 const std::u16string& pasted_text,
+                 size_t index,
+                 base::TimeTicks match_selection_timestamp = base::TimeTicks());
+
   // Returns true if a query to an autocomplete provider is currently
   // in progress.  This logic should in the future live in
   // AutocompleteController but resides here for now.  This method is used by
diff --git a/components/omnibox/browser/omnibox_edit_model_unittest.cc b/components/omnibox/browser/omnibox_edit_model_unittest.cc
index e4c7774..a407bc3 100644
--- a/components/omnibox/browser/omnibox_edit_model_unittest.cc
+++ b/components/omnibox/browser/omnibox_edit_model_unittest.cc
@@ -66,8 +66,8 @@
     model->SetUserText(url_text);
   }
   model->OnSetFocus(false);
-  model->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
-                   std::u16string(), 0);
+  model->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
+                             std::u16string(), 0);
 }
 
 }  // namespace
@@ -368,14 +368,14 @@
 
   model()->OnSetFocus(false);  // Avoids DCHECK in OpenMatch().
   model()->SetUserText(u"http://abcd");
-  model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB,
-                     alternate_nav_url, std::u16string(), 0);
+  model()->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB,
+                               alternate_nav_url, std::u16string(), 0);
   EXPECT_TRUE(AutocompleteInput::HasHTTPScheme(
       edit_model_delegate_->alternate_nav_match().fill_into_edit));
 
   model()->SetUserText(u"abcd");
-  model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB,
-                     alternate_nav_url, std::u16string(), 0);
+  model()->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB,
+                               alternate_nav_url, std::u16string(), 0);
   EXPECT_TRUE(AutocompleteInput::HasHTTPScheme(
       edit_model_delegate_->alternate_nav_match().fill_into_edit));
 }
@@ -585,7 +585,7 @@
                                                u"bar");
 
   model()->OnControlKeyChanged(true);
-  model()->AcceptInput(WindowOpenDisposition::UNKNOWN);
+  model()->OpenSelection();
   OmniboxEditModel::State state = model()->GetStateForTabSwitch();
   EXPECT_EQ(GURL("http://www.foo.com/"),
             state.autocomplete_input.canonicalized_url());
@@ -601,7 +601,7 @@
                               std::u16string(), {});
 
   model()->OnControlKeyChanged(true);
-  model()->AcceptInput(WindowOpenDisposition::UNKNOWN);
+  model()->OpenSelection();
   OmniboxEditModel::State state = model()->GetStateForTabSwitch();
   EXPECT_EQ(GURL("http://www.foobar.com/"),
             state.autocomplete_input.canonicalized_url());
@@ -616,7 +616,7 @@
   model()->Revert();
 
   model()->OnControlKeyChanged(true);
-  model()->AcceptInput(WindowOpenDisposition::UNKNOWN);
+  model()->OpenSelection();
   OmniboxEditModel::State state = model()->GetStateForTabSwitch();
   EXPECT_EQ(GURL("https://www.example.com/"),
             state.autocomplete_input.canonicalized_url());
@@ -1327,23 +1327,23 @@
 
   model()->OnSetFocus(false);  // Avoids DCHECK in OpenMatch().
   model()->SetUserText(u"http://abcd");
-  model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
-                     std::u16string(), 0);
+  model()->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB,
+                               GURL(), std::u16string(), 0);
   EXPECT_EQ(edit_model_delegate_->disposition(),
             WindowOpenDisposition::SWITCH_TO_TAB);
 
   // Suggestions not from the Open Tab Provider or not from keyword mode should
   // not change the disposition.
   match.from_keyword = false;
-  model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
-                     std::u16string(), 0);
+  model()->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB,
+                               GURL(), std::u16string(), 0);
   EXPECT_EQ(edit_model_delegate_->disposition(),
             WindowOpenDisposition::CURRENT_TAB);
 
   match.provider = model()->autocomplete_controller()->search_provider();
   match.from_keyword = true;
-  model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, GURL(),
-                     std::u16string(), 0);
+  model()->OpenMatchForTesting(match, WindowOpenDisposition::CURRENT_TAB,
+                               GURL(), std::u16string(), 0);
   EXPECT_EQ(edit_model_delegate_->disposition(),
             WindowOpenDisposition::CURRENT_TAB);
 }
diff --git a/components/omnibox/browser/omnibox_view.cc b/components/omnibox/browser/omnibox_view.cc
index c6afac0f..26bf056 100644
--- a/components/omnibox/browser/omnibox_view.cc
+++ b/components/omnibox/browser/omnibox_view.cc
@@ -165,19 +165,6 @@
 
 OmniboxView::~OmniboxView() = default;
 
-void OmniboxView::OpenMatch(const AutocompleteMatch& match,
-                            WindowOpenDisposition disposition,
-                            const GURL& alternate_nav_url,
-                            const std::u16string& pasted_text,
-                            size_t selected_line,
-                            base::TimeTicks match_selection_timestamp) {
-  // Invalid URLs such as chrome://history can end up here.
-  if (!match.destination_url.is_valid() || !model_)
-    return;
-  model_->OpenMatch(match, disposition, alternate_nav_url, pasted_text,
-                    selected_line, match_selection_timestamp);
-}
-
 bool OmniboxView::IsEditingOrEmpty() const {
   return (model_.get() && model_->user_input_in_progress()) ||
          (GetOmniboxTextLength() == 0);
diff --git a/components/omnibox/browser/omnibox_view.h b/components/omnibox/browser/omnibox_view.h
index 6db9619..f8410d9c 100644
--- a/components/omnibox/browser/omnibox_view.h
+++ b/components/omnibox/browser/omnibox_view.h
@@ -29,7 +29,6 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gfx/range/range.h"
 
-class GURL;
 class OmniboxEditModelDelegate;
 class OmniboxViewMacTest;
 class OmniboxEditModel;
@@ -64,24 +63,6 @@
   // Called when any relevant state changes other than changing tabs.
   virtual void Update() = 0;
 
-  // Asks the browser to load the specified match, using the supplied
-  // disposition. |alternate_nav_url|, if non-empty, contains the
-  // alternate navigation URL for for this match. See comments on
-  // AutocompleteResult::GetAlternateNavURL().
-  //
-  // |pasted_text| should only be set if this call is due to a
-  // Paste-And-Go/Search action.
-  //
-  // |selected_line| is passed to SendOpenNotification(); see comments there.
-  //
-  // This may close the popup.
-  virtual void OpenMatch(const AutocompleteMatch& match,
-                         WindowOpenDisposition disposition,
-                         const GURL& alternate_nav_url,
-                         const std::u16string& pasted_text,
-                         size_t selected_line,
-                         base::TimeTicks match_selection_timestamp);
-
   // Returns the current text of the edit control, which could be the
   // "temporary" text set by the popup, the "permanent" text set by the
   // browser, or just whatever the user has currently typed.
diff --git a/components/omnibox/browser/test_omnibox_view.h b/components/omnibox/browser/test_omnibox_view.h
index d8bc506..20213dc7 100644
--- a/components/omnibox/browser/test_omnibox_view.h
+++ b/components/omnibox/browser/test_omnibox_view.h
@@ -38,12 +38,6 @@
 
   // OmniboxView:
   void Update() override {}
-  void OpenMatch(const AutocompleteMatch& match,
-                 WindowOpenDisposition disposition,
-                 const GURL& alternate_nav_url,
-                 const std::u16string& pasted_text,
-                 size_t selected_line,
-                 base::TimeTicks match_selection_timestamp) override {}
   std::u16string GetText() const override;
   void SetWindowTextAndCaretPos(const std::u16string& text,
                                 size_t caret_pos,
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 7fd4b33..089c33e 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -4,6 +4,8 @@
 
 #include "components/omnibox/common/omnibox_features.h"
 
+#include <string>
+
 #include "base/feature_list.h"
 #include "build/build_config.h"
 
@@ -443,6 +445,30 @@
              "OmniboxSteadyStateBackgroundColor",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Specifies the GM3 omnibox background color in Dark Mode.
+const base::FeatureParam<std::string> kOmniboxDarkBackgroundColor(
+    &omnibox::kOmniboxSteadyStateBackgroundColor,
+    "OmniboxDarkBackgroundColor",
+    "0x2A2A2A");
+
+// Specifies the GM3 omnibox background color in Dark Mode (on-hover).
+const base::FeatureParam<std::string> kOmniboxDarkBackgroundColorHovered(
+    &omnibox::kOmniboxSteadyStateBackgroundColor,
+    "OmniboxDarkBackgroundColorHovered",
+    "0x4C4C4B");
+
+// Specifies the GM3 omnibox background color in Light Mode.
+const base::FeatureParam<std::string> kOmniboxLightBackgroundColor(
+    &omnibox::kOmniboxSteadyStateBackgroundColor,
+    "OmniboxLightBackgroundColor",
+    "0xEBEFF7");
+
+// Specifies the GM3 omnibox background color in Light Mode (on-hover).
+const base::FeatureParam<std::string> kOmniboxLightBackgroundColorHovered(
+    &omnibox::kOmniboxSteadyStateBackgroundColor,
+    "OmniboxLightBackgroundColorHovered",
+    "0xE3E7F0");
+
 // If enabled, Omnibox "steady state" height is increased from 28 dp to 34 dp to
 // match GM3 guidelines.
 BASE_FEATURE(kOmniboxSteadyStateHeight,
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index a7fd2986..0797b0c 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_OMNIBOX_COMMON_OMNIBOX_FEATURES_H_
 #define COMPONENTS_OMNIBOX_COMMON_OMNIBOX_FEATURES_H_
 
+#include <string>
+
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 
@@ -103,7 +105,16 @@
 // Omnibox UI - these affect the UI or function of the location bar (not the
 // popup).
 BASE_DECLARE_FEATURE(kOmniboxAssistantVoiceSearch);
+
 BASE_DECLARE_FEATURE(kOmniboxSteadyStateBackgroundColor);
+// These feature params are located here, as opposed to omnibox_field_trial.h,
+// in order to permit inclusion into (non-Omnibox) color mixer code.
+extern const base::FeatureParam<std::string> kOmniboxDarkBackgroundColor;
+extern const base::FeatureParam<std::string> kOmniboxDarkBackgroundColorHovered;
+extern const base::FeatureParam<std::string> kOmniboxLightBackgroundColor;
+extern const base::FeatureParam<std::string>
+    kOmniboxLightBackgroundColorHovered;
+
 BASE_DECLARE_FEATURE(kOmniboxSteadyStateHeight);
 BASE_DECLARE_FEATURE(kOmniboxSteadyStateTextStyle);
 BASE_DECLARE_FEATURE(kDiscardTemporaryInputOnTabSwitch);
diff --git a/components/optimization_guide/core/hints_fetcher.cc b/components/optimization_guide/core/hints_fetcher.cc
index e6978b5..c5b3153 100644
--- a/components/optimization_guide/core/hints_fetcher.cc
+++ b/components/optimization_guide/core/hints_fetcher.cc
@@ -106,6 +106,11 @@
   if (active_url_loader_) {
     if (hints_fetched_callback_)
       std::move(hints_fetched_callback_).Run(absl::nullopt);
+
+    OPTIMIZATION_GUIDE_LOG(
+        optimization_guide_common::mojom::LogSource::HINTS,
+        optimization_guide_logger_,
+        "No hints fetched: HintsFetcher ActiveRequestCanceled");
     base::UmaHistogramExactLinear(
         "OptimizationGuide.HintsFetcher.GetHintsRequest."
         "ActiveRequestCanceled." +
@@ -177,6 +182,7 @@
         optimization_types,
     optimization_guide::proto::RequestContext request_context,
     const std::string& locale,
+    bool skip_cache,
     HintsFetchedCallback hints_fetched_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_GT(optimization_types.size(), 0u);
@@ -194,7 +200,7 @@
   }
 
   std::vector<std::string> filtered_hosts =
-      GetSizeLimitedHostsDueForHintsRefresh(hosts);
+      GetSizeLimitedHostsDueForHintsRefresh(hosts, skip_cache);
   std::vector<GURL> valid_urls = GetSizeLimitedURLsForFetching(urls);
   if (filtered_hosts.empty() && valid_urls.empty()) {
     OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
@@ -308,7 +314,8 @@
   // |active_url_loader_| is destroyed.
   active_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
       url_loader_factory_.get(),
-      base::BindOnce(&HintsFetcher::OnURLLoadComplete, base::Unretained(this)));
+      base::BindOnce(&HintsFetcher::OnURLLoadComplete, base::Unretained(this),
+                     skip_cache));
 
   hints_fetched_callback_ = std::move(hints_fetched_callback);
   hosts_fetched_ = filtered_hosts;
@@ -317,7 +324,8 @@
 
 void HintsFetcher::HandleResponse(const std::string& get_hints_response_data,
                                   int net_status,
-                                  int response_code) {
+                                  int response_code,
+                                  bool skip_cache) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   std::unique_ptr<proto::GetHintsResponse> get_hints_response =
@@ -346,6 +354,15 @@
         "OptimizationGuide.HintsFetcher.GetHintsRequest.FetchLatency." +
             GetStringNameForRequestContext(request_context_),
         fetch_latency);
+    if (skip_cache) {
+      RecordRequestStatusHistogram(request_context_,
+                                   HintsFetcherRequestStatus::kSuccess);
+      std::move(hints_fetched_callback_).Run(std::move(get_hints_response));
+
+      return;
+    }
+
+    // Check cache duration and update.
     base::TimeDelta valid_duration =
         features::StoredFetchedHintsFreshnessDuration();
     if (get_hints_response->has_max_cache_duration()) {
@@ -421,6 +438,7 @@
 
 // Callback is only invoked if |active_url_loader_| is bound and still alive.
 void HintsFetcher::OnURLLoadComplete(
+    bool skip_cache,
     std::unique_ptr<std::string> response_body) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -435,7 +453,8 @@
   // handling may destroy |this|.
   active_url_loader_.reset();
 
-  HandleResponse(response_body ? *response_body : "", net_error, response_code);
+  HandleResponse(response_body ? *response_body : "", net_error, response_code,
+                 skip_cache);
 }
 
 // Returns the subset of URLs from |urls| for which the URL is considered
@@ -473,7 +492,8 @@
 }
 
 std::vector<std::string> HintsFetcher::GetSizeLimitedHostsDueForHintsRefresh(
-    const std::vector<std::string>& hosts) const {
+    const std::vector<std::string>& hosts,
+    bool skip_cache) const {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   const base::Value::Dict& hosts_fetched =
@@ -506,6 +526,10 @@
           base::StrCat({"Skipped adding invalid host:", host}));
       continue;
     }
+    if (skip_cache) {
+      target_hosts.push_back(host);
+      continue;
+    }
 
     bool host_hints_due_for_refresh = true;
 
diff --git a/components/optimization_guide/core/hints_fetcher.h b/components/optimization_guide/core/hints_fetcher.h
index a68ec1c..9c8e741c 100644
--- a/components/optimization_guide/core/hints_fetcher.h
+++ b/components/optimization_guide/core/hints_fetcher.h
@@ -83,12 +83,13 @@
   // |hints_fetched_callback| is run once when the outcome of this request is
   // determined (whether a request was actually sent or not). Virtualized for
   // testing. Hints fetcher may fetch hints for only a subset of the provided
-  // |hosts|. |hosts| should be an ordered list in descending order of
-  // probability that the hints are needed for that host. Only supported |urls|
-  // will be included in the fetch. |urls| is an ordered list in descending
-  // order of probability that a hint will be needed for the URL. The supplied
-  // optimization types will be included in the request, if empty no fetch will
-  // be made.
+  // |hosts|. A host may be skipped when too many are requested, or when it
+  // already has a result cached, unless |skip_cache| is specified. |hosts|
+  // should be an ordered list in descending order of probability that the hints
+  // are needed for that host. Only supported |urls| will be included in the
+  // fetch. |urls| is an ordered list in descending order of probability that a
+  // hint will be needed for the URL. The supplied optimization types will be
+  // included in the request, if empty no fetch will be made.
   virtual bool FetchOptimizationGuideServiceHints(
       const std::vector<std::string>& hosts,
       const std::vector<GURL>& urls,
@@ -96,6 +97,7 @@
           optimization_types,
       optimization_guide::proto::RequestContext request_context,
       const std::string& locale,
+      bool skip_cache,
       HintsFetchedCallback hints_fetched_callback);
 
   // Set |time_clock_| for testing.
@@ -125,7 +127,8 @@
 
  private:
   // URL loader completion callback.
-  void OnURLLoadComplete(std::unique_ptr<std::string> response_body);
+  void OnURLLoadComplete(bool skip_cache,
+                         std::unique_ptr<std::string> response_body);
 
   // Handles the response from the remote Optimization Guide Service.
   // |response| is the response body, |status| is the
@@ -133,7 +136,8 @@
   // response code (if available).
   void HandleResponse(const std::string& response,
                       int status,
-                      int response_code);
+                      int response_code,
+                      bool skip_cache);
 
   // Stores the hosts in |hosts_fetched_| in the
   // HintsFetcherHostsSuccessfullyFetched dictionary pref. The value stored for
@@ -151,7 +155,8 @@
   // refreshed. The count of returned hosts is limited to
   // features::MaxHostsForOptimizationGuideServiceHintsFetch().
   std::vector<std::string> GetSizeLimitedHostsDueForHintsRefresh(
-      const std::vector<std::string>& hosts) const;
+      const std::vector<std::string>& hosts,
+      bool skip_cache) const;
 
   // Used to hold the callback while the SimpleURLLoader performs the request
   // asynchronously.
diff --git a/components/optimization_guide/core/hints_fetcher_unittest.cc b/components/optimization_guide/core/hints_fetcher_unittest.cc
index b3232ed5..dcd1408 100644
--- a/components/optimization_guide/core/hints_fetcher_unittest.cc
+++ b/components/optimization_guide/core/hints_fetcher_unittest.cc
@@ -99,10 +99,12 @@
 
  protected:
   bool FetchHints(const std::vector<std::string>& hosts,
-                  const std::vector<GURL>& urls) {
+                  const std::vector<GURL>& urls,
+                  bool skip_cache = false) {
     bool status = hints_fetcher_->FetchOptimizationGuideServiceHints(
         hosts, urls, {optimization_guide::proto::NOSCRIPT},
         optimization_guide::proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, "en-US",
+        skip_cache,
         base::BindOnce(&HintsFetcherTest::OnHintsFetched,
                        base::Unretained(this)));
     RunUntilIdle();
@@ -175,7 +177,7 @@
        FetchOptimizationGuideServiceHintsLogsHistogramUponExiting) {
   base::HistogramTester histogram_tester;
 
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   ResetHintsFetcher();
 
@@ -189,7 +191,7 @@
   base::HistogramTester histogram_tester;
 
   std::string response_content;
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -219,8 +221,8 @@
   // |fetch_in_progress_| should cause early exit.
   {
     base::HistogramTester histogram_tester;
-    EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
-    EXPECT_FALSE(FetchHints({"bar.com"}, {} /* urls */));
+    EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
+    EXPECT_FALSE(FetchHints({"bar.com"}, /*urls=*/{}));
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsFetcher.RequestStatus.BatchUpdateActiveTabs",
         HintsFetcherRequestStatus::kFetcherBusy, 1);
@@ -231,7 +233,7 @@
     base::HistogramTester histogram_tester;
     std::string response_content;
     SimulateResponse(response_content, net::HTTP_OK);
-    EXPECT_TRUE(FetchHints({"bar.com"}, {} /* urls */));
+    EXPECT_TRUE(FetchHints({"bar.com"}, /*urls=*/{}));
     histogram_tester.ExpectUniqueSample(
         "OptimizationGuide.HintsFetcher.RequestStatus.BatchUpdateActiveTabs",
         HintsFetcherRequestStatus::kSuccess, 1);
@@ -249,19 +251,19 @@
   std::string response_content;
   // Fetch back to back without waiting for Fetch to complete,
   // |fetch_in_progress_| should cause early exit.
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
 
   // Once response arrives, check to make sure that the fetch for the same host
   // is not started again.
   SimulateResponse(response_content, net::HTTP_OK);
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
   // Ensure a new fetch for a different host can start.
-  EXPECT_TRUE(FetchHints({"bar.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"bar.com"}, /*urls=*/{}));
   SimulateResponse(response_content, net::HTTP_OK);
 
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_FALSE(FetchHints({"bar.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_FALSE(FetchHints({"bar.com"}, /*urls=*/{}));
 
   std::vector<std::string> hosts{"foo.com", "bar.com"};
   // Advancing the clock so that it's still one hour before the hints need to be
@@ -270,28 +272,28 @@
                      features::GetHostHintsFetchRefreshDuration() -
                      base::Hours(1));
 
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_FALSE(FetchHints({"bar.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_FALSE(FetchHints({"bar.com"}, /*urls=*/{}));
 
   // Advancing the clock by a little bit more than 1 hour so that the hints are
   // now due for refresh.
   test_clock.Advance(base::Minutes(61));
 
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_FALSE(FetchHints({"bar.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_FALSE(FetchHints({"bar.com"}, /*urls=*/{}));
   SimulateResponse(response_content, net::HTTP_OK);
 
   // Hints should not be fetched again for foo.com since they were fetched
   // recently. Hints should still be fetched for bar.com.
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_TRUE(FetchHints({"bar.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_TRUE(FetchHints({"bar.com"}, /*urls=*/{}));
   SimulateResponse(response_content, net::HTTP_OK);
 
   // Hints should not be fetched again for foo.com and bar.com since they were
   // fetched recently. For baz.com, hints should be fetched again.
-  EXPECT_FALSE(FetchHints({"foo.com"}, {} /* urls */));
-  EXPECT_FALSE(FetchHints({"bar.com"}, {} /* urls */));
-  EXPECT_TRUE(FetchHints({"baz.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
+  EXPECT_FALSE(FetchHints({"bar.com"}, /*urls=*/{}));
+  EXPECT_TRUE(FetchHints({"baz.com"}, /*urls=*/{}));
   proto::GetHintsResponse response;
   response.mutable_max_cache_duration()->set_seconds(60 * 60 * 24 * 20);
   response.SerializeToString(&response_content);
@@ -302,7 +304,28 @@
   test_clock.Advance(features::StoredFetchedHintsFreshnessDuration());
 
   // Max cache duration from response should be used for pref instead.
-  EXPECT_FALSE(FetchHints({"baz.com"}, {} /* urls */));
+  EXPECT_FALSE(FetchHints({"baz.com"}, /*urls=*/{}));
+}
+
+// Tests that the hints are refreshed again for hosts for whom hints were
+// fetched recently.
+TEST_P(HintsFetcherTest, FetchIgnoresCache) {
+  if (!ShouldPersistHintsToDisk()) {
+    return;
+  }
+  base::SimpleTestClock test_clock;
+  SetTimeClockForTesting(&test_clock);
+
+  std::string response_content;
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
+
+  // Once response arrives, check to make sure that the fetch for the same host
+  // is not started again.
+  SimulateResponse(response_content, net::HTTP_OK);
+  EXPECT_FALSE(FetchHints({"foo.com"}, /*urls=*/{}));
+
+  // Specifying to ignore caching should still fetch.
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}, /*skip_cache=*/true));
 }
 
 // Tests 404 response from request.
@@ -311,7 +334,7 @@
 
   std::string response_content;
 
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
 
   // Send a 404 to HintsFetcher.
   SimulateResponse(response_content, net::HTTP_NOT_FOUND);
@@ -329,7 +352,7 @@
   base::HistogramTester histogram_tester;
 
   std::string response_content = "not proto";
-  EXPECT_TRUE(FetchHints({"foo.com"}, {} /* urls */));
+  EXPECT_TRUE(FetchHints({"foo.com"}, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_FALSE(hints_fetched());
@@ -346,7 +369,7 @@
   std::vector<std::string> hosts{"host1.com", "host2.com"};
   std::string response_content;
 
-  EXPECT_TRUE(FetchHints(hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -374,7 +397,7 @@
   std::vector<std::string> hosts{"host1.com", "host2.com"};
   std::string response_content;
 
-  EXPECT_TRUE(FetchHints(hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_NOT_FOUND));
   EXPECT_FALSE(hints_fetched());
@@ -393,7 +416,7 @@
   std::vector<std::string> hosts{"host1.com", "host2.com"};
   std::string response_content;
 
-  EXPECT_TRUE(FetchHints(hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -423,7 +446,7 @@
   std::vector<std::string> hosts{"host1.com", "host2.com"};
   std::string response_content;
 
-  EXPECT_TRUE(FetchHints(hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -471,7 +494,7 @@
 
   // Fetch hints for new hosts.
   std::vector<std::string> hosts_valid{"host3.com", "hosts4.com"};
-  EXPECT_TRUE(FetchHints(hosts_valid, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts_valid, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -525,7 +548,7 @@
 
   std::vector<std::string> hosts_valid{"host3.com", "host4.com"};
 
-  EXPECT_TRUE(FetchHints(hosts_valid, {} /* urls */));
+  EXPECT_TRUE(FetchHints(hosts_valid, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -560,7 +583,7 @@
 
   std::vector<std::string> extra_hosts{"extra1.com", "extra2.com"};
 
-  EXPECT_TRUE(FetchHints(extra_hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(extra_hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -594,7 +617,7 @@
   all_hosts.push_back("extra1.com");
   all_hosts.push_back("extra2.com");
 
-  EXPECT_TRUE(FetchHints(all_hosts, {} /* urls */));
+  EXPECT_TRUE(FetchHints(all_hosts, /*urls=*/{}));
   VerifyHasPendingFetchRequests();
   EXPECT_TRUE(SimulateResponse(response_content, net::HTTP_OK));
   EXPECT_TRUE(hints_fetched());
@@ -691,7 +714,7 @@
   base::HistogramTester histogram_tester;
   std::string response_content;
 
-  EXPECT_FALSE(FetchHints({} /* hosts */, {} /* urls */));
+  EXPECT_FALSE(FetchHints({} /* hosts */, /*urls=*/{}));
   EXPECT_FALSE(hints_fetched());
   histogram_tester.ExpectUniqueSample(
       "OptimizationGuide.HintsFetcher.RequestStatus.BatchUpdateActiveTabs",
diff --git a/components/optimization_guide/core/hints_manager.cc b/components/optimization_guide/core/hints_manager.cc
index 635edd5..47daefe 100644
--- a/components/optimization_guide/core/hints_manager.cc
+++ b/components/optimization_guide/core/hints_manager.cc
@@ -4,6 +4,7 @@
 
 #include "components/optimization_guide/core/hints_manager.h"
 
+#include <cstddef>
 #include <string>
 #include <utility>
 
@@ -16,6 +17,7 @@
 #include "base/metrics/histogram_macros_local.h"
 #include "base/notreached.h"
 #include "base/rand_util.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/thread_pool.h"
@@ -44,7 +46,7 @@
 #include "components/optimization_guide/core/optimization_metadata.h"
 #include "components/optimization_guide/core/tab_url_provider.h"
 #include "components/optimization_guide/core/top_host_provider.h"
-#include "components/optimization_guide/proto/models.pb.h"
+#include "components/optimization_guide/proto/hints.pb.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -243,8 +245,7 @@
 // service.
 void MaybeLogGetHintRequestInfo(
     proto::RequestContext request_context,
-    const base::flat_set<proto::OptimizationType>&
-        registered_optimization_types,
+    const base::flat_set<proto::OptimizationType>& requested_optimization_types,
     const std::vector<GURL>& urls_to_fetch,
     const std::vector<std::string>& hosts_to_fetch,
     OptimizationGuideLogger* optimization_guide_logger) {
@@ -258,12 +259,16 @@
                          optimization_guide_logger,
                          "Registered Optimization Types: ");
   if (optimization_guide_logger->ShouldEnableDebugLogs()) {
-    for (const auto& optimization_type : registered_optimization_types) {
-      OPTIMIZATION_GUIDE_LOGGER(
-          optimization_guide_common::mojom::LogSource::HINTS,
-          optimization_guide_logger)
-          << "Optimization Type: " << optimization_type;
+    std::vector<std::string> pieces = {"Optimization Type:"};
+    for (const auto& optimization_type : requested_optimization_types) {
+      pieces.push_back(
+          base::StrCat({" ", proto::OptimizationType_Name(optimization_type)}));
     }
+
+    OPTIMIZATION_GUIDE_LOGGER(
+        optimization_guide_common::mojom::LogSource::HINTS,
+        optimization_guide_logger)
+        << base::StrCat(pieces);
     OPTIMIZATION_GUIDE_LOG(optimization_guide_common::mojom::LogSource::HINTS,
                            optimization_guide_logger, " URLs and Hosts: ");
     for (const auto& url : urls_to_fetch) {
@@ -293,7 +298,8 @@
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
     std::unique_ptr<PushNotificationManager> push_notification_manager,
     OptimizationGuideLogger* optimization_guide_logger)
-    : failed_component_version_(
+    : optimization_guide_logger_(optimization_guide_logger),
+      failed_component_version_(
           GetPendingOptimizationHintsComponentVersionFromPref(pref_service)),
       is_off_the_record_(is_off_the_record),
       application_locale_(application_locale),
@@ -311,7 +317,6 @@
       top_host_provider_(top_host_provider),
       tab_url_provider_(tab_url_provider),
       push_notification_manager_(std::move(push_notification_manager)),
-      optimization_guide_logger_(optimization_guide_logger),
       clock_(base::DefaultClock::GetInstance()),
       background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
           {base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
@@ -751,6 +756,7 @@
   active_tabs_batch_update_hints_fetcher_->FetchOptimizationGuideServiceHints(
       top_hosts, active_tab_urls_to_refresh, registered_optimization_types_,
       proto::CONTEXT_BATCH_UPDATE_ACTIVE_TABS, application_locale_,
+      /*skip_cache=*/false,
       base::BindOnce(&HintsManager::OnHintsForActiveTabsFetched,
                      weak_ptr_factory_.GetWeakPtr(), top_hosts_set,
                      base::flat_set<GURL>(active_tab_urls_to_refresh.begin(),
@@ -914,14 +920,15 @@
   std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
       CreateAndTrackBatchUpdateHintsFetcher();
 
-  // Use the batch update hints fetcher for fetches off the SRP since we are
-  // not fetching for the current navigation.
+  // Use the batch update hints fetcher since we are not fetching for the
+  // current navigation.
   //
   // Caller does not expect to be notified when relevant hints have been fetched
   // and stored.
   request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
       target_hosts.vector(), target_urls.vector(),
       registered_optimization_types_, request_context, application_locale_,
+      /*skip_cache=*/false,
       base::BindOnce(
           &HintsManager::OnBatchUpdateHintsFetched,
           weak_ptr_factory_.GetWeakPtr(), request_id_and_fetcher.first,
@@ -1055,39 +1062,52 @@
     OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  // TODO(crbug/1275612): Check whether we have consent to fetch.
-
   // This set contains URLs that require some information to be fetched, whether
   // that be a URL-keyed hint or a host-keyed hint.
-  base::flat_set<GURL> urls_with_pending_callback;
+  base::flat_set<proto::OptimizationType> unregistered_optimization_types;
+  for (const auto& type : optimization_types) {
+    if (registered_optimization_types_.find(type) ==
+        registered_optimization_types_.end()) {
+      unregistered_optimization_types.insert(type);
+    }
+  }
 
+  base::flat_set<GURL> urls_with_pending_callback;
   InsertionOrderedSet<GURL> urls_to_fetch;
   InsertionOrderedSet<std::string> hosts_to_fetch;
   for (const auto& url : urls) {
-    if (!hint_cache_->HasURLKeyedEntryForURL(url)) {
+    // Only do cache checks when all optimization types requested are
+    // registered.
+    if (!unregistered_optimization_types.empty()) {
       urls_to_fetch.insert(url);
-      urls_with_pending_callback.insert(url);
-    }
-    // We check for the hint being loaded mostly for code simplicity. If we just
-    // check for the presence in the cache and the hint wasn't loaded, we need
-    // to run the load callback and then invoke the callbacks. However, as the
-    // fetch will just ignore the host if cached, this is ok since the callback
-    // from the fetch will initiate the loading of the hint and the resulting
-    // callback to be run.
-    if (!hint_cache_->HasHint(url.host()) ||
-        (hint_cache_->GetHostKeyedHintIfLoaded(url.host()) == nullptr)) {
       hosts_to_fetch.insert(url.host());
       urls_with_pending_callback.insert(url);
-    }
+    } else {
+      if (!hint_cache_->HasURLKeyedEntryForURL(url)) {
+        urls_to_fetch.insert(url);
+        urls_with_pending_callback.insert(url);
+      }
+      // We check for the hint being loaded mostly for code simplicity. If we
+      // just check for the presence in the cache and the hint wasn't loaded, we
+      // need to run the load callback and then invoke the callbacks. However,
+      // as the fetch will just ignore the host if cached, this is ok since the
+      // callback from the fetch will initiate the loading of the hint and the
+      // resulting callback to be run.
+      if (!hint_cache_->HasHint(url.host()) ||
+          (hint_cache_->GetHostKeyedHintIfLoaded(url.host()) == nullptr)) {
+        hosts_to_fetch.insert(url.host());
+        urls_with_pending_callback.insert(url);
+      }
 
-    if (!urls_with_pending_callback.contains(url)) {
-      // Only get decisions if we know we do not need to fetch for anything.
-      base::flat_map<proto::OptimizationType,
-                     OptimizationGuideDecisionWithMetadata>
-          decisions =
-              GetDecisionsWithCachedInformationForURLAndOptimizationTypes(
-                  url, optimization_types);
-      callback.Run(url, decisions);
+      if (!urls_with_pending_callback.contains(url)) {
+        // Only get decisions if we know we do not need to fetch for anything.
+        base::flat_map<proto::OptimizationType,
+                       OptimizationGuideDecisionWithMetadata>
+            decisions =
+                GetDecisionsWithCachedInformationForURLAndOptimizationTypes(
+                    url, optimization_types);
+        callback.Run(url, decisions);
+      }
     }
   }
 
@@ -1096,7 +1116,7 @@
     return;
   }
 
-  MaybeLogGetHintRequestInfo(request_context, registered_optimization_types_,
+  MaybeLogGetHintRequestInfo(request_context, optimization_types,
                              urls_to_fetch.vector(), hosts_to_fetch.vector(),
                              optimization_guide_logger_);
 
@@ -1104,8 +1124,8 @@
   std::pair<int32_t, HintsFetcher*> request_id_and_fetcher =
       CreateAndTrackBatchUpdateHintsFetcher();
   request_id_and_fetcher.second->FetchOptimizationGuideServiceHints(
-      hosts_to_fetch.vector(), urls_to_fetch.vector(),
-      registered_optimization_types_, request_context, application_locale_,
+      hosts_to_fetch.vector(), urls_to_fetch.vector(), optimization_types,
+      request_context, application_locale_, /*skip_cache=*/true,
       base::BindOnce(&HintsManager::OnBatchUpdateHintsFetched,
                      weak_ptr_factory_.GetWeakPtr(),
                      request_id_and_fetcher.first, request_context,
@@ -1141,6 +1161,62 @@
   }
 }
 
+void HintsManager::ProcessAndInvokeOnDemandHintsCallbacks(
+    std::unique_ptr<proto::GetHintsResponse> response,
+    const base::flat_set<GURL> requested_urls,
+    const base::flat_set<proto::OptimizationType> optimization_types,
+    OnDemandOptimizationGuideDecisionRepeatingCallback callback) {
+  // TODO(b/266694081): Reintroduce client side in memory cache sharded by
+  // context and store here.
+  base::flat_map<std::string, const proto::Hint*> url_mapped_hints;
+  base::flat_map<std::string, const proto::Hint*> host_mapped_hints;
+  for (const auto& hint : response->hints()) {
+    if (hint.key().empty()) {
+      continue;
+    }
+    switch (hint.key_representation()) {
+      case proto::HOST:
+        host_mapped_hints[hint.key()] = &hint;
+
+        break;
+      case proto::FULL_URL:
+        if (IsValidURLForURLKeyedHint(GURL(hint.key()))) {
+          url_mapped_hints[hint.key()] = &hint;
+        }
+        break;
+      case proto::HOST_SUFFIX:
+        // Old component versions if not updated could potentially have
+        // HOST_SUFFIX hints. Just skip over them.
+        break;
+      case proto::HASHED_HOST:
+        // The server should not send hints with hashed host key.
+        NOTREACHED();
+        break;
+      case proto::REPRESENTATION_UNSPECIFIED:
+        NOTREACHED();
+        break;
+    }
+  }
+
+  for (const auto& url : requested_urls) {
+    base::flat_map<proto::OptimizationType,
+                   OptimizationGuideDecisionWithMetadata>
+        decisions;
+
+    for (const auto optimization_type : optimization_types) {
+      OptimizationMetadata metadata;
+      OptimizationTypeDecision type_decision = CanApplyOptimization(
+          url, optimization_type, url_mapped_hints[url.spec()],
+          host_mapped_hints[url.host()], /*skip_cache=*/true, &metadata);
+      OptimizationGuideDecision decision =
+          GetOptimizationGuideDecisionFromOptimizationTypeDecision(
+              type_decision);
+      decisions[optimization_type] = {decision, metadata};
+    }
+    callback.Run(url, decisions);
+  }
+}
+
 void HintsManager::OnBatchUpdateHintsFetched(
     int32_t request_id,
     proto::RequestContext request_context,
@@ -1165,6 +1241,29 @@
     return;
   }
 
+  base::flat_set<proto::OptimizationType> unregistered_optimization_types;
+  for (const auto& type : optimization_types) {
+    if (registered_optimization_types_.find(type) ==
+        registered_optimization_types_.end()) {
+      unregistered_optimization_types.insert(type);
+    }
+  }
+  if (!unregistered_optimization_types.empty()) {
+    // When opt types are not registered, process and return decisions directly
+    // without caching.
+    ProcessAndInvokeOnDemandHintsCallbacks(std::move(*get_hints_response),
+                                           urls_requested, optimization_types,
+                                           callback);
+    if (optimization_guide_logger_->ShouldEnableDebugLogs()) {
+      OPTIMIZATION_GUIDE_LOGGER(
+          optimization_guide_common::mojom::LogSource::HINTS,
+          optimization_guide_logger_)
+          << "ProcessAndInvokeOnDemandHintsCallbacks processing response "
+             "directly for "
+          << request_context;
+    }
+    return;
+  }
   // TODO(crbug/1278015): Figure out if the update time duration is the right
   // one.
   hint_cache_->UpdateFetchedHints(
@@ -1262,6 +1361,20 @@
     const GURL& navigation_url,
     proto::OptimizationType optimization_type,
     OptimizationMetadata* optimization_metadata) {
+  return CanApplyOptimization(
+      navigation_url, optimization_type,
+      hint_cache_->GetURLKeyedHint(navigation_url),
+      hint_cache_->GetHostKeyedHintIfLoaded(navigation_url.host()), false,
+      optimization_metadata);
+}
+
+OptimizationTypeDecision HintsManager::CanApplyOptimization(
+    const GURL& navigation_url,
+    proto::OptimizationType optimization_type,
+    const proto::Hint* url_keyed_hint,
+    const proto::Hint* host_keyed_hint,
+    bool skip_cache,
+    OptimizationMetadata* optimization_metadata) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   ScopedCanApplyOptimizationLogger scoped_logger(
@@ -1270,14 +1383,14 @@
   if (optimization_metadata)
     *optimization_metadata = {};
 
-  // Ensure that developers register their opt types before asking the
-  // optimization guide for data for that type.
-  DCHECK(registered_optimization_types_.find(optimization_type) !=
-         registered_optimization_types_.end());
-  // If the type is not registered, we probably don't have a hint for it, so
-  // just return.
-  if (registered_optimization_types_.find(optimization_type) ==
-      registered_optimization_types_.end()) {
+  bool is_optimization_type_registered =
+      registered_optimization_types_.find(optimization_type) !=
+      registered_optimization_types_.end();
+
+  // If type is not registered AND neither hint is passed in, we probably do not
+  // have a hint, so just return.
+  if (!is_optimization_type_registered &&
+      (url_keyed_hint == nullptr && host_keyed_hint == nullptr)) {
     scoped_logger.set_type_decision(OptimizationTypeDecision::kNoHintAvailable);
     return OptimizationTypeDecision::kNoHintAvailable;
   }
@@ -1328,9 +1441,7 @@
     return OptimizationTypeDecision::kHadOptimizationFilterButNotLoadedInTime;
   }
 
-  // First, check if the optimization type is allowlisted by a URL-keyed hint.
-  const proto::Hint* url_keyed_hint =
-      hint_cache_->GetURLKeyedHint(navigation_url);
+  // First, check if the optimization type is allowlisted by the URL-keyed hint.
   if (url_keyed_hint) {
     DCHECK_EQ(url_keyed_hint->page_hints_size(), 1);
     if (url_keyed_hint->page_hints_size() > 0) {
@@ -1346,9 +1457,14 @@
     }
   }
 
-  // Check if we have a hint already loaded for this navigation.
-  const proto::Hint* loaded_hint = hint_cache_->GetHostKeyedHintIfLoaded(host);
-  if (!loaded_hint) {
+  if (!host_keyed_hint) {
+    // The request was not for cached information, so no data is available.
+    if (skip_cache) {
+      scoped_logger.set_type_decision(
+          OptimizationTypeDecision::kNoHintAvailable);
+      return OptimizationTypeDecision::kNoHintAvailable;
+    }
+    // Otherwise check and record cache status.
     if (hint_cache_->HasHint(host)) {
       // If we do not have a hint already loaded and we do not have one in the
       // cache, we do not know what to do with the URL so just return.
@@ -1373,7 +1489,7 @@
     return OptimizationTypeDecision::kNoHintAvailable;
   }
 
-  if (IsOptimizationTypeAllowed(loaded_hint->allowlisted_optimizations(),
+  if (IsOptimizationTypeAllowed(host_keyed_hint->allowlisted_optimizations(),
                                 optimization_type, optimization_metadata)) {
     scoped_logger.set_type_decision(OptimizationTypeDecision::kAllowedByHint);
     if (optimization_metadata && !optimization_metadata->empty())
@@ -1382,7 +1498,8 @@
   }
 
   const proto::PageHint* matched_page_hint =
-      loaded_hint ? FindPageHintForURL(navigation_url, loaded_hint) : nullptr;
+      host_keyed_hint ? FindPageHintForURL(navigation_url, host_keyed_hint)
+                      : nullptr;
   if (!matched_page_hint) {
     scoped_logger.set_type_decision(
         OptimizationTypeDecision::kNotAllowedByHint);
@@ -1553,7 +1670,7 @@
                              optimization_guide_logger_);
   bool fetch_attempted = it->second->FetchOptimizationGuideServiceHints(
       hosts, urls, registered_optimization_types_,
-      proto::CONTEXT_PAGE_NAVIGATION, application_locale_,
+      proto::CONTEXT_PAGE_NAVIGATION, application_locale_, /*skip_cache=*/false,
       base::BindOnce(&HintsManager::OnPageNavigationHintsFetched,
                      weak_ptr_factory_.GetWeakPtr(),
                      navigation_data->GetWeakPtr(), url,
diff --git a/components/optimization_guide/core/hints_manager.h b/components/optimization_guide/core/hints_manager.h
index 55b6cda..b3aaaaf 100644
--- a/components/optimization_guide/core/hints_manager.h
+++ b/components/optimization_guide/core/hints_manager.h
@@ -109,6 +109,18 @@
       proto::OptimizationType optimization_type,
       OptimizationMetadata* optimization_metadata);
 
+  // Returns the OptimizationTypeDecision based on the given parameters.
+  // |optimization_metadata| will be populated, if applicable. The decision will
+  // be computed on |url_keyed_hint| or |host_keyed_hint| if possible.
+  // |skip_cache| will be used to determine if the decision is unknown.
+  OptimizationTypeDecision CanApplyOptimization(
+      const GURL& navigation_url,
+      proto::OptimizationType optimization_type,
+      const proto::Hint* url_keyed_hint,
+      const proto::Hint* host_keyed_hint,
+      bool skip_cache,
+      OptimizationMetadata* optimization_metadata);
+
   // Invokes |callback| with the decision for the URL contained in |url| and
   // |optimization_type|, when sufficient information has been collected to
   // make the decision.
@@ -303,6 +315,14 @@
       const base::flat_set<proto::OptimizationType>& optimization_types,
       OnDemandOptimizationGuideDecisionRepeatingCallback callback);
 
+  // Invokes |callback| for |requested_urls| and |optimization_types| based on
+  // what is contained in |response|.
+  void ProcessAndInvokeOnDemandHintsCallbacks(
+      std::unique_ptr<proto::GetHintsResponse> response,
+      const base::flat_set<GURL> requested_urls,
+      const base::flat_set<proto::OptimizationType> optimization_types,
+      OnDemandOptimizationGuideDecisionRepeatingCallback callback);
+
   // Called when the hints for a navigation have been fetched from the remote
   // Optimization Guide Service and are ready for parsing. This is used when
   // fetching hints in real-time. |navigation_url| is the URL associated with
@@ -402,6 +422,12 @@
     return active_tabs_batch_update_hints_fetcher_.get();
   }
 
+  // The logger that plumbs the debug logs to the optimization guide
+  // internals page. Not owned. Guaranteed to outlive |this|, since the logger
+  // and |this| are owned by the optimization guide keyed service.
+  raw_ptr<OptimizationGuideLogger, DanglingUntriaged>
+      optimization_guide_logger_;
+
   // The information of the latest component delivered by
   // |optimization_guide_service_|.
   absl::optional<HintsComponentInfo> hints_component_info_;
@@ -487,12 +513,6 @@
   // what to do through the implemented Delegate above.
   std::unique_ptr<PushNotificationManager> push_notification_manager_;
 
-  // The logger that plumbs the debug logs to the optimization guide
-  // internals page. Not owned. Guaranteed to outlive |this|, since the logger
-  // and |this| are owned by the optimization guide keyed service.
-  raw_ptr<OptimizationGuideLogger, DanglingUntriaged>
-      optimization_guide_logger_;
-
   // The clock used to schedule fetching from the remote Optimization Guide
   // Service.
   raw_ptr<const base::Clock> clock_;
diff --git a/components/optimization_guide/core/hints_manager_unittest.cc b/components/optimization_guide/core/hints_manager_unittest.cc
index 4e4c4901..0dec2bf1 100644
--- a/components/optimization_guide/core/hints_manager_unittest.cc
+++ b/components/optimization_guide/core/hints_manager_unittest.cc
@@ -32,6 +32,7 @@
 #include "components/optimization_guide/core/proto_database_provider_test_base.h"
 #include "components/optimization_guide/core/tab_url_provider.h"
 #include "components/optimization_guide/core/top_host_provider.h"
+#include "components/optimization_guide/proto/hints.pb.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -205,6 +206,7 @@
       const base::flat_set<proto::OptimizationType>& optimization_types,
       proto::RequestContext request_context,
       const std::string& locale,
+      bool skip_cache,
       HintsFetchedCallback hints_fetched_callback) override {
     HintsFetcherEndState fetch_state =
         num_fetches_requested_ < static_cast<int>(fetch_states_.size())
@@ -3138,6 +3140,121 @@
       "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 2, 2);
 }
 
+TEST_F(HintsManagerFetchingTest,
+       CanApplyOptimizationOnDemandNoRegistrationAlwaysFetches) {
+  base::HistogramTester histogram_tester;
+
+  InitializeWithDefaultConfig("1.0.0.0");
+
+  hints_manager()->SetHintsFetcherFactoryForTesting(
+      BuildTestHintsFetcherFactory(
+          {HintsFetcherEndState::kFetchSuccessWithURLHints}));
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_url_keyed_hint()},
+      {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::BindRepeating(
+          [](base::RunLoop* run_loop, const GURL& url,
+             const base::flat_map<proto::OptimizationType,
+                                  OptimizationGuideDecisionWithMetadata>&
+                 decisions) {
+            ASSERT_EQ(decisions.size(), 2u);
+            auto it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES);
+            ASSERT_TRUE(it != decisions.end());
+            EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision);
+            EXPECT_TRUE(it->second.metadata.any_metadata().has_value());
+
+            it = decisions.find(proto::NOSCRIPT);
+            ASSERT_TRUE(it != decisions.end());
+            EXPECT_EQ(OptimizationGuideDecision::kFalse, it->second.decision);
+
+            run_loop->Quit();
+          },
+          run_loop.get()));
+  run_loop->Run();
+
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 1);
+}
+
+TEST_F(HintsManagerFetchingTest,
+       CanApplyOptimizationOnDemandNoRegistrationFetchFailure) {
+  base::HistogramTester histogram_tester;
+
+  InitializeWithDefaultConfig("1.0.0.0");
+
+  hints_manager()->SetHintsFetcherFactoryForTesting(
+      BuildTestHintsFetcherFactory({HintsFetcherEndState::kFetchFailed}));
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_url_keyed_hint()}, {proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::BindRepeating(
+          [](base::RunLoop* run_loop, const GURL& url,
+             const base::flat_map<proto::OptimizationType,
+                                  OptimizationGuideDecisionWithMetadata>&
+                 decisions) {
+            ASSERT_EQ(decisions.size(), 1u);
+            auto it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES);
+            ASSERT_TRUE(it != decisions.end());
+            EXPECT_EQ(OptimizationGuideDecision::kFalse, it->second.decision);
+
+            run_loop->Quit();
+          },
+          run_loop.get()));
+  run_loop->Run();
+
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 1);
+}
+
+TEST_F(HintsManagerFetchingTest,
+       CanApplyOptimizationOnDemandMixedRegistrations) {
+  base::HistogramTester histogram_tester;
+
+  InitializeWithDefaultConfig("1.0.0.0");
+
+  hints_manager()->RegisterOptimizationTypes({proto::NOSCRIPT});
+  hints_manager()->SetHintsFetcherFactoryForTesting(
+      BuildTestHintsFetcherFactory(
+          {HintsFetcherEndState::kFetchSuccessWithURLHints}));
+  // Make sure NOSCRIPT is cached and loaded.
+  auto navigation_data =
+      CreateTestNavigationData(url_with_url_keyed_hint(), {proto::NOSCRIPT});
+  CallOnNavigationStartOrRedirect(navigation_data.get(), base::DoNothing());
+  RunUntilIdle();
+
+  std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
+  hints_manager()->CanApplyOptimizationOnDemand(
+      {url_with_hints()}, {proto::NOSCRIPT, proto::COMPRESS_PUBLIC_IMAGES},
+      proto::RequestContext::CONTEXT_BOOKMARKS,
+      base::BindRepeating(
+          [](base::RunLoop* run_loop, const GURL& url,
+             const base::flat_map<proto::OptimizationType,
+                                  OptimizationGuideDecisionWithMetadata>&
+                 decisions) {
+            ASSERT_EQ(decisions.size(), 2u);
+            auto it = decisions.find(proto::NOSCRIPT);
+            ASSERT_TRUE(it != decisions.end());
+            // Even though this is cached/loaded, the on-demand call does not
+            // have a result from the server.
+            EXPECT_EQ(OptimizationGuideDecision::kFalse, it->second.decision);
+
+            it = decisions.find(proto::COMPRESS_PUBLIC_IMAGES);
+            ASSERT_TRUE(it != decisions.end());
+            EXPECT_EQ(OptimizationGuideDecision::kTrue, it->second.decision);
+            EXPECT_TRUE(it->second.metadata.any_metadata().has_value());
+
+            run_loop->Quit();
+          },
+          run_loop.get()));
+  run_loop->Run();
+
+  histogram_tester.ExpectTotalCount(
+      "OptimizationGuide.HintsManager.ConcurrentBatchUpdateFetches", 1);
+}
+
 TEST_F(
     HintsManagerFetchingTest,
     CanApplyOptimizationOnDemandDecisionMultipleTypesBothHostAndURLKeyedMixedFetch) {
diff --git a/components/page_load_metrics/browser/BUILD.gn b/components/page_load_metrics/browser/BUILD.gn
index 1813447..b6af267 100644
--- a/components/page_load_metrics/browser/BUILD.gn
+++ b/components/page_load_metrics/browser/BUILD.gn
@@ -34,6 +34,8 @@
     "observers/fenced_frames_page_load_metrics_observer.h",
     "observers/prerender_page_load_metrics_observer.cc",
     "observers/prerender_page_load_metrics_observer.h",
+    "observers/privacy_sandbox_ads_page_load_metrics_observer.cc",
+    "observers/privacy_sandbox_ads_page_load_metrics_observer.h",
     "observers/same_origin_page_load_metrics_observer.cc",
     "observers/same_origin_page_load_metrics_observer.h",
     "observers/shared_storage_page_load_metrics_observer.cc",
@@ -131,6 +133,7 @@
     "observers/fenced_frames_page_load_metrics_observer_unittest.cc",
     "observers/page_load_metrics_observer_content_test_harness.cc",
     "observers/page_load_metrics_observer_content_test_harness.h",
+    "observers/privacy_sandbox_ads_page_load_metrics_observer_unittest.cc",
     "observers/use_counter/at_most_once_enum_uma_deferrer_unittest.cc",
     "observers/use_counter_page_load_metrics_observer_unittest.cc",
     "page_load_metrics_forward_observer_unittest.cc",
diff --git a/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.cc b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.cc
new file mode 100644
index 0000000..042faef
--- /dev/null
+++ b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.cc
@@ -0,0 +1,213 @@
+// 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/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h"
+
+#include <vector>
+
+#include "base/metrics/histogram_functions.h"
+#include "base/strings/strcat.h"
+#include "base/time/time.h"
+#include "components/page_load_metrics/browser/observers/core/largest_contentful_paint_handler.h"
+#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/page_load_metrics_observer_delegate.h"
+#include "components/page_load_metrics/browser/page_load_metrics_observer_interface.h"
+#include "components/page_load_metrics/browser/page_load_metrics_util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
+#include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom.h"
+
+namespace {
+
+using FeatureType = blink::mojom::UseCounterFeatureType;
+using WebFeature = blink::mojom::WebFeature;
+
+constexpr char kHistogramPrivacySandboxAdsFirstInputDelay4Prefix[] =
+    "PageLoad.Clients.PrivacySandboxAds.InteractiveTiming.FirstInputDelay4.";
+
+constexpr char
+    kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaintPrefix[] =
+        "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
+        "NavigationToFirstContentfulPaint.";
+
+constexpr char
+    kHistogramPrivacySandboxAdsNavigationToLargestContentfulPaint2Prefix[] =
+        "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
+        "NavigationToLargestContentfulPaint2.";
+
+constexpr char kHistogramPrivacySandboxAdsMaxCumulativeShiftScorePrefix[] =
+    "PageLoad.Clients.PrivacySandboxAds.LayoutInstability."
+    "MaxCumulativeShiftScore.SessionWindow.Gap1000ms.Max5000ms2.";
+
+}  // namespace
+
+// static
+std::string PrivacySandboxAdsPageLoadMetricsObserver::GetHistogramName(
+    const char* prefix,
+    PrivacySandboxAdsApi api) {
+  const char* suffix;
+  switch (api) {
+    case PrivacySandboxAdsApi::kAttributionReporting:
+      suffix = "AttributionReporting";
+      break;
+    case PrivacySandboxAdsApi::kFencedFrames:
+      suffix = "FencedFrames";
+      break;
+    case PrivacySandboxAdsApi::kFledge:
+      suffix = "Fledge";
+      break;
+    case PrivacySandboxAdsApi::kPrivateAggregation:
+      suffix = "PrivateAggregation";
+      break;
+    case PrivacySandboxAdsApi::kSharedStorage:
+      suffix = "SharedStorage";
+      break;
+    case PrivacySandboxAdsApi::kTopics:
+      suffix = "Topics";
+      break;
+  }
+  return base::StrCat({prefix, suffix});
+}
+
+const char* PrivacySandboxAdsPageLoadMetricsObserver::GetObserverName() const {
+  static const char kName[] = "PrivacySandboxAdsPageLoadMetricsObserver";
+  return kName;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+PrivacySandboxAdsPageLoadMetricsObserver::OnFencedFramesStart(
+    content::NavigationHandle* navigation_handle,
+    const GURL& currently_committed_url) {
+  // `OnFeaturesUsageObserved()` needs observer level forwarding.
+  return FORWARD_OBSERVING;
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+PrivacySandboxAdsPageLoadMetricsObserver::OnPrerenderStart(
+    content::NavigationHandle* navigation_handle,
+    const GURL& currently_committed_url) {
+  return CONTINUE_OBSERVING;
+}
+
+void PrivacySandboxAdsPageLoadMetricsObserver::OnFirstInputInPage(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  if (!page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
+          timing.interactive_timing->first_input_timestamp, GetDelegate())) {
+    return;
+  }
+
+  for (PrivacySandboxAdsApi api : used_privacy_sandbox_ads_apis_) {
+    base::UmaHistogramCustomTimes(
+        GetHistogramName(kHistogramPrivacySandboxAdsFirstInputDelay4Prefix,
+                         api),
+        timing.interactive_timing->first_input_delay.value(),
+        base::Milliseconds(1), base::Seconds(60), 50);
+  }
+}
+
+void PrivacySandboxAdsPageLoadMetricsObserver::OnFirstContentfulPaintInPage(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  if (!page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
+          timing.paint_timing->first_contentful_paint, GetDelegate())) {
+    return;
+  }
+
+  for (PrivacySandboxAdsApi api : used_privacy_sandbox_ads_apis_) {
+    base::UmaHistogramCustomTimes(
+        GetHistogramName(
+            kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaintPrefix,
+            api),
+        timing.paint_timing->first_contentful_paint.value(),
+        base::Milliseconds(10), base::Minutes(10), 100);
+  }
+}
+
+void PrivacySandboxAdsPageLoadMetricsObserver::OnComplete(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  RecordSessionEndHistograms(timing);
+}
+
+page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+PrivacySandboxAdsPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
+    const page_load_metrics::mojom::PageLoadTiming& timing) {
+  RecordSessionEndHistograms(timing);
+  return STOP_OBSERVING;
+}
+
+void PrivacySandboxAdsPageLoadMetricsObserver::OnFeaturesUsageObserved(
+    content::RenderFrameHost* rfh,
+    const std::vector<blink::UseCounterFeature>& features) {
+  for (const blink::UseCounterFeature& feature : features) {
+    if (feature.type() != FeatureType::kWebFeature) {
+      continue;
+    }
+
+    absl::optional<PrivacySandboxAdsApi> api;
+    switch (static_cast<WebFeature>(feature.value())) {
+      case WebFeature::kConversionAPIAll:
+        api = PrivacySandboxAdsApi::kAttributionReporting;
+        break;
+      case WebFeature::kHTMLFencedFrameElement:
+        api = PrivacySandboxAdsApi::kFencedFrames;
+        break;
+      case WebFeature::kV8Navigator_RunAdAuction_Method:
+      case WebFeature::kV8Navigator_JoinAdInterestGroup_Method:
+        api = PrivacySandboxAdsApi::kFledge;
+        break;
+      case WebFeature::kPrivateAggregationApiAll:
+        api = PrivacySandboxAdsApi::kPrivateAggregation;
+        break;
+      case WebFeature::kSharedStorageAPI_SharedStorage_DOMReference:
+      case WebFeature::kSharedStorageAPI_Run_Method:
+      case WebFeature::kSharedStorageAPI_SelectURL_Method:
+        api = PrivacySandboxAdsApi::kSharedStorage;
+        break;
+      case WebFeature::kTopicsAPI_BrowsingTopics_Method:
+        api = PrivacySandboxAdsApi::kTopics;
+        break;
+      default:
+        break;
+    }
+    if (api.has_value()) {
+      used_privacy_sandbox_ads_apis_.Put(*api);
+    }
+  }
+}
+
+void PrivacySandboxAdsPageLoadMetricsObserver::RecordSessionEndHistograms(
+    const page_load_metrics::mojom::PageLoadTiming& main_frame_timing) {
+  if (!GetDelegate().DidCommit()) {
+    return;
+  }
+
+  const page_load_metrics::ContentfulPaintTimingInfo& largest_contentful_paint =
+      GetDelegate()
+          .GetLargestContentfulPaintHandler()
+          .MergeMainFrameAndSubframes();
+  if (largest_contentful_paint.ContainsValidTime() &&
+      page_load_metrics::WasStartedInForegroundOptionalEventInForeground(
+          largest_contentful_paint.Time(), GetDelegate())) {
+    for (PrivacySandboxAdsApi api : used_privacy_sandbox_ads_apis_) {
+      base::UmaHistogramCustomTimes(
+          GetHistogramName(
+              kHistogramPrivacySandboxAdsNavigationToLargestContentfulPaint2Prefix,
+              api),
+          largest_contentful_paint.Time().value(), base::Milliseconds(10),
+          base::Minutes(10), 100);
+    }
+  }
+
+  const page_load_metrics::NormalizedCLSData& normalized_cls_data =
+      GetDelegate().GetNormalizedCLSData(
+          page_load_metrics::PageLoadMetricsObserverDelegate::BfcacheStrategy::
+              ACCUMULATE);
+  if (!normalized_cls_data.data_tainted) {
+    for (PrivacySandboxAdsApi api : used_privacy_sandbox_ads_apis_) {
+      page_load_metrics::UmaMaxCumulativeShiftScoreHistogram10000x(
+          GetHistogramName(
+              kHistogramPrivacySandboxAdsMaxCumulativeShiftScorePrefix, api),
+          normalized_cls_data);
+    }
+  }
+}
diff --git a/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h
new file mode 100644
index 0000000..a674016
--- /dev/null
+++ b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h
@@ -0,0 +1,70 @@
+// 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_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRIVACY_SANDBOX_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
+#define COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRIVACY_SANDBOX_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
+
+#include <string>
+
+#include "base/containers/enum_set.h"
+#include "components/page_load_metrics/browser/page_load_metrics_observer.h"
+#include "components/page_load_metrics/common/page_load_metrics.mojom-forward.h"
+
+namespace page_load_metrics {
+class PrivacySandboxAdsPageLoadMetricsObserverTest;
+}  // namespace page_load_metrics
+
+class PrivacySandboxAdsPageLoadMetricsObserver
+    : public page_load_metrics::PageLoadMetricsObserver {
+ public:
+  PrivacySandboxAdsPageLoadMetricsObserver() = default;
+  ~PrivacySandboxAdsPageLoadMetricsObserver() override = default;
+
+ private:
+  friend class page_load_metrics::PrivacySandboxAdsPageLoadMetricsObserverTest;
+
+  enum class PrivacySandboxAdsApi {
+    kAttributionReporting,
+    kFencedFrames,
+    kFledge,
+    kPrivateAggregation,
+    kSharedStorage,
+    kTopics,
+
+    kMinValue = kAttributionReporting,
+    kMaxValue = kTopics,
+  };
+
+  static std::string GetHistogramName(const char* prefix, PrivacySandboxAdsApi);
+
+  // page_load_metrics::PageLoadMetricsObserver:
+  const char* GetObserverName() const override;
+  ObservePolicy OnFencedFramesStart(
+      content::NavigationHandle* navigation_handle,
+      const GURL& currently_committed_url) override;
+  ObservePolicy OnPrerenderStart(content::NavigationHandle* navigation_handle,
+                                 const GURL& currently_committed_url) override;
+  void OnFirstInputInPage(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnFirstContentfulPaintInPage(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnComplete(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  page_load_metrics::PageLoadMetricsObserver::ObservePolicy
+  FlushMetricsOnAppEnterBackground(
+      const page_load_metrics::mojom::PageLoadTiming& timing) override;
+  void OnFeaturesUsageObserved(
+      content::RenderFrameHost* rfh,
+      const std::vector<blink::UseCounterFeature>& features) override;
+
+  void RecordSessionEndHistograms(
+      const page_load_metrics::mojom::PageLoadTiming& main_frame_timing);
+
+  base::EnumSet<PrivacySandboxAdsApi,
+                PrivacySandboxAdsApi::kMinValue,
+                PrivacySandboxAdsApi::kMaxValue>
+      used_privacy_sandbox_ads_apis_;
+};
+
+#endif  // COMPONENTS_PAGE_LOAD_METRICS_BROWSER_OBSERVERS_PRIVACY_SANDBOX_ADS_PAGE_LOAD_METRICS_OBSERVER_H_
diff --git a/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer_unittest.cc b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer_unittest.cc
new file mode 100644
index 0000000..abd5bff
--- /dev/null
+++ b/components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer_unittest.cc
@@ -0,0 +1,211 @@
+// 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/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h"
+
+#include <memory>
+#include <vector>
+
+#include "base/containers/enum_set.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/ranges/algorithm.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/time/time.h"
+#include "components/page_load_metrics/browser/observers/page_load_metrics_observer_content_test_harness.h"
+#include "components/page_load_metrics/browser/observers/page_load_metrics_observer_tester.h"
+#include "components/page_load_metrics/browser/page_load_tracker.h"
+#include "components/page_load_metrics/common/page_load_metrics.mojom.h"
+#include "components/page_load_metrics/common/page_load_timing.h"
+#include "components/page_load_metrics/common/test/page_load_metrics_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/use_counter/use_counter_feature.h"
+#include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom.h"
+#include "third_party/blink/public/mojom/use_counter/use_counter_feature.mojom.h"
+#include "url/gurl.h"
+
+namespace page_load_metrics {
+
+namespace {
+
+using WebFeature = blink::mojom::WebFeature;
+
+constexpr char kHistogramPrivacySandboxAdsFirstInputDelay4Prefix[] =
+    "PageLoad.Clients.PrivacySandboxAds.InteractiveTiming.FirstInputDelay4.";
+
+constexpr char
+    kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaintPrefix[] =
+        "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
+        "NavigationToFirstContentfulPaint.";
+
+constexpr char
+    kHistogramPrivacySandboxAdsNavigationToLargestContentfulPaint2Prefix[] =
+        "PageLoad.Clients.PrivacySandboxAds.PaintTiming."
+        "NavigationToLargestContentfulPaint2.";
+
+constexpr char kHistogramPrivacySandboxAdsMaxCumulativeShiftScorePrefix[] =
+    "PageLoad.Clients.PrivacySandboxAds.LayoutInstability."
+    "MaxCumulativeShiftScore.SessionWindow.Gap1000ms.Max5000ms2.";
+
+constexpr char kTestUrl[] = "https://a.test/test";
+
+struct TestCase {
+  const char* name;
+  base::flat_set<WebFeature> web_features;
+};
+
+}  // namespace
+
+class PrivacySandboxAdsPageLoadMetricsObserverTest
+    : public PageLoadMetricsObserverContentTestHarness,
+      public ::testing::WithParamInterface<TestCase> {
+ protected:
+  void HistogramTest(const base::flat_set<WebFeature>& web_features) {
+    NavigateAndCommit(GURL(kTestUrl));
+
+    std::vector<blink::UseCounterFeature> enabled_features;
+    for (auto feature : web_features) {
+      enabled_features.emplace_back(
+          blink::mojom::UseCounterFeatureType::kWebFeature,
+          static_cast<int>(feature));
+    }
+    tester()->SimulateFeaturesUpdate(enabled_features);
+
+    PopulateTimingForHistograms();
+    tester()->NavigateToUntrackedUrl();
+
+    static const base::flat_map<PrivacySandboxAdsApi, std::vector<WebFeature>>
+        kFeaturesMap = {
+            {PrivacySandboxAdsApi::kAttributionReporting,
+             {WebFeature::kConversionAPIAll}},
+            {PrivacySandboxAdsApi::kFencedFrames,
+             {WebFeature::kHTMLFencedFrameElement}},
+            {PrivacySandboxAdsApi::kFledge,
+             {WebFeature::kV8Navigator_RunAdAuction_Method,
+              WebFeature::kV8Navigator_JoinAdInterestGroup_Method}},
+            {PrivacySandboxAdsApi::kPrivateAggregation,
+             {WebFeature::kPrivateAggregationApiAll}},
+            {PrivacySandboxAdsApi::kSharedStorage,
+             {WebFeature::kSharedStorageAPI_SharedStorage_DOMReference,
+              WebFeature::kSharedStorageAPI_Run_Method,
+              WebFeature::kSharedStorageAPI_SelectURL_Method}},
+            {PrivacySandboxAdsApi::kTopics,
+             {WebFeature::kTopicsAPI_BrowsingTopics_Method}},
+        };
+
+    for (auto api :
+         base::EnumSet<PrivacySandboxAdsApi, PrivacySandboxAdsApi::kMinValue,
+                       PrivacySandboxAdsApi::kMaxValue>::All()) {
+      int expected_count = base::ranges::any_of(
+          kFeaturesMap.at(api), [&](WebFeature web_feature) {
+            return web_features.contains(web_feature);
+          });
+
+      tester()->histogram_tester().ExpectTotalCount(
+          PrivacySandboxAdsPageLoadMetricsObserver::GetHistogramName(
+              kHistogramPrivacySandboxAdsFirstInputDelay4Prefix, api),
+          expected_count);
+
+      tester()->histogram_tester().ExpectTotalCount(
+          PrivacySandboxAdsPageLoadMetricsObserver::GetHistogramName(
+              kHistogramPrivacySandboxAdsNavigationToFirstContentfulPaintPrefix,
+              api),
+          expected_count);
+
+      tester()->histogram_tester().ExpectTotalCount(
+          PrivacySandboxAdsPageLoadMetricsObserver::GetHistogramName(
+              kHistogramPrivacySandboxAdsNavigationToLargestContentfulPaint2Prefix,
+              api),
+          expected_count);
+
+      tester()->histogram_tester().ExpectTotalCount(
+          PrivacySandboxAdsPageLoadMetricsObserver::GetHistogramName(
+              kHistogramPrivacySandboxAdsMaxCumulativeShiftScorePrefix, api),
+          expected_count);
+    }
+  }
+
+ private:
+  using PrivacySandboxAdsApi =
+      PrivacySandboxAdsPageLoadMetricsObserver::PrivacySandboxAdsApi;
+
+  void SetUp() override { PageLoadMetricsObserverContentTestHarness::SetUp(); }
+
+  void RegisterObservers(PageLoadTracker* tracker) override {
+    tracker->AddObserver(
+        std::make_unique<PrivacySandboxAdsPageLoadMetricsObserver>());
+  }
+
+  void PopulateTimingForHistograms() {
+    mojom::PageLoadTiming timing;
+    InitPageLoadTimingForTest(&timing);
+    timing.navigation_start = base::Time::Now();
+    timing.parse_timing->parse_stop = base::Milliseconds(50);
+    timing.paint_timing->first_image_paint = base::Milliseconds(80);
+    timing.paint_timing->first_contentful_paint = base::Milliseconds(100);
+
+    auto largest_contentful_paint = mojom::LargestContentfulPaintTiming::New();
+    largest_contentful_paint->largest_image_paint = base::Milliseconds(100);
+    largest_contentful_paint->largest_image_paint_size = 100;
+    timing.paint_timing->largest_contentful_paint =
+        std::move(largest_contentful_paint);
+
+    timing.interactive_timing->first_input_delay = base::Milliseconds(10);
+    timing.interactive_timing->first_input_timestamp = base::Milliseconds(4780);
+
+    PopulateRequiredTimingFields(&timing);
+    tester()->SimulateTimingUpdate(timing);
+  }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    PrivacySandboxAdsPageLoadMetricsObserverTest,
+    ::testing::Values(
+        TestCase{
+            .name = "none",
+            .web_features = {},
+        },
+        TestCase{
+            .name = "all",
+            .web_features =
+                {WebFeature::kConversionAPIAll,
+                 WebFeature::kHTMLFencedFrameElement,
+                 WebFeature::kV8Navigator_RunAdAuction_Method,
+                 WebFeature::kV8Navigator_JoinAdInterestGroup_Method,
+                 WebFeature::kPrivateAggregationApiAll,
+                 WebFeature::kSharedStorageAPI_SharedStorage_DOMReference,
+                 WebFeature::kSharedStorageAPI_Run_Method,
+                 WebFeature::kSharedStorageAPI_SelectURL_Method,
+                 WebFeature::kTopicsAPI_BrowsingTopics_Method},
+        },
+        TestCase{
+            .name = "run_ad_auction",
+            .web_features = {WebFeature::kV8Navigator_RunAdAuction_Method},
+        },
+        TestCase{
+            .name = "join_ad_interest_group",
+            .web_features =
+                {WebFeature::kV8Navigator_JoinAdInterestGroup_Method},
+        },
+        TestCase{
+            .name = "shared_storage_dom_reference",
+            .web_features =
+                {WebFeature::kSharedStorageAPI_SharedStorage_DOMReference},
+        },
+        TestCase{
+            .name = "shared_storage_run",
+            .web_features = {WebFeature::kSharedStorageAPI_Run_Method},
+        },
+        TestCase{
+            .name = "shared_storage_select_url",
+            .web_features = {WebFeature::kSharedStorageAPI_SelectURL_Method},
+        }),
+    [](const auto& info) { return info.param.name; });  // test name generator
+
+TEST_P(PrivacySandboxAdsPageLoadMetricsObserverTest, VerifyMetrics) {
+  HistogramTest(GetParam().web_features);
+}
+
+}  // namespace page_load_metrics
diff --git a/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc b/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
index 0e4634b..c482a93 100644
--- a/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
+++ b/components/page_load_metrics/browser/page_load_metrics_embedder_base.cc
@@ -13,6 +13,7 @@
 #include "components/page_load_metrics/browser/observers/early_hints_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/fenced_frames_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/prerender_page_load_metrics_observer.h"
+#include "components/page_load_metrics/browser/observers/privacy_sandbox_ads_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/same_origin_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/shared_storage_page_load_metrics_observer.h"
 #include "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
@@ -45,6 +46,8 @@
       tracker->AddObserver(
           std::make_unique<SharedStoragePageLoadMetricsObserver>());
     }
+    tracker->AddObserver(
+        std::make_unique<PrivacySandboxAdsPageLoadMetricsObserver>());
   }
   // Allow the embedder to register any embedder-specific observers
   RegisterEmbedderObservers(tracker);
diff --git a/components/policy/resources/templates/policy_definitions/DateAndTime/CalendarIntegrationEnabled.yaml b/components/policy/resources/templates/policy_definitions/DateAndTime/CalendarIntegrationEnabled.yaml
index bcfa7ab0..aea0eb9 100644
--- a/components/policy/resources/templates/policy_definitions/DateAndTime/CalendarIntegrationEnabled.yaml
+++ b/components/policy/resources/templates/policy_definitions/DateAndTime/CalendarIntegrationEnabled.yaml
@@ -12,8 +12,8 @@
 features:
   dynamic_refresh: true
   per_profile: true
-future_on:
-- chrome_os
+supported_on:
+- chrome_os:113-
 items:
 - caption: Enable <ph name="GOOGLE_CALENDAR_NAME">Google Calendar</ph> Integration.
   value: true
diff --git a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.cc b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.cc
index e2d0d9c..94061e4 100644
--- a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.cc
@@ -511,11 +511,12 @@
   using ExperimentThreatType = ClientSafeBrowsingReportRequest::
       HashRealTimeExperimentDetails::ExperimentThreatType;
   ExperimentThreatType hash_database_threat_type =
-      GetExperimentDetailsThreatType(hash_database_results.threat_type);
+      GetExperimentDetailsThreatType(hash_database_results.threat_type).value();
   ExperimentThreatType url_realtime_threat_type =
-      GetExperimentDetailsThreatType(url_real_time_results.threat_type);
+      GetExperimentDetailsThreatType(url_real_time_results.threat_type).value();
   ExperimentThreatType hash_realtime_threat_type =
-      GetExperimentDetailsThreatType(hash_real_time_results.threat_type);
+      GetExperimentDetailsThreatType(hash_real_time_results.threat_type)
+          .value();
   if (hash_database_threat_type == url_realtime_threat_type &&
       url_realtime_threat_type == hash_realtime_threat_type) {
     // If all 3 mechanisms agree on the response threat type, there is nothing
@@ -539,9 +540,14 @@
   url_realtime_details.set_threat_type(url_realtime_threat_type);
   url_realtime_details.set_matched_global_cache(
       check->url_real_time_details.matched_global_cache.value());
-  url_realtime_details.set_locally_cached_results_threat_type(
-      GetExperimentDetailsThreatType(
-          url_real_time_results.locally_cached_results_threat_type));
+  absl::optional<ExperimentThreatType>
+      url_realtime_locally_cached_results_threat_type =
+          GetExperimentDetailsThreatType(
+              url_real_time_results.locally_cached_results_threat_type);
+  if (url_realtime_locally_cached_results_threat_type.has_value()) {
+    url_realtime_details.set_locally_cached_results_threat_type(
+        url_realtime_locally_cached_results_threat_type.value());
+  }
   *experiment_details.mutable_url_realtime_details() = url_realtime_details;
   // 4. Gather hash real-time details.
   ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
@@ -549,9 +555,14 @@
   hash_realtime_details.set_threat_type(hash_realtime_threat_type);
   hash_realtime_details.set_matched_global_cache(
       check->hash_real_time_details.matched_global_cache.value());
-  hash_realtime_details.set_locally_cached_results_threat_type(
-      GetExperimentDetailsThreatType(
-          hash_real_time_results.locally_cached_results_threat_type));
+  absl::optional<ExperimentThreatType>
+      hash_realtime_locally_cached_results_threat_type =
+          GetExperimentDetailsThreatType(
+              hash_real_time_results.locally_cached_results_threat_type);
+  if (hash_realtime_locally_cached_results_threat_type.has_value()) {
+    hash_realtime_details.set_locally_cached_results_threat_type(
+        hash_realtime_locally_cached_results_threat_type.value());
+  }
   *experiment_details.mutable_hash_realtime_details() = hash_realtime_details;
   // 5. Fill in experiment details.
   *report->mutable_hash_real_time_experiment_details() = experiment_details;
@@ -563,13 +574,12 @@
                                 std::move(report), ping_manager_on_ui_));
 }
 // static:
-ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
-    ExperimentThreatType
-    SafeBrowsingLookupMechanismExperimenter::GetExperimentDetailsThreatType(
-        absl::optional<SBThreatType> threat_type) {
+absl::optional<ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
+                   ExperimentThreatType>
+SafeBrowsingLookupMechanismExperimenter::GetExperimentDetailsThreatType(
+    absl::optional<SBThreatType> threat_type) {
   if (!threat_type.has_value()) {
-    return ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
-        SAFE_OR_OTHER;
+    return absl::nullopt;
   }
   switch (threat_type.value()) {
     case SBThreatType::SB_THREAT_TYPE_URL_PHISHING:
diff --git a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
index 16756877..2edc443a 100644
--- a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
+++ b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter.h
@@ -312,9 +312,9 @@
       bool hash_real_time_result);
   // Converts a |SBThreatType| to the one used for the hash real-time experiment
   // CSBRR.
-  static ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
-      ExperimentThreatType
-      GetExperimentDetailsThreatType(absl::optional<SBThreatType> threat_type);
+  static absl::optional<ClientSafeBrowsingReportRequest::
+                            HashRealTimeExperimentDetails::ExperimentThreatType>
+  GetExperimentDetailsThreatType(absl::optional<SBThreatType> threat_type);
   // Returns details on whether the result caused a delay, and if so, by how
   // much.
   DelayInformation GetDelayInformation(MechanismResults& results) const;
diff --git a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter_unittest.cc b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter_unittest.cc
index e94213b7..93deab1 100644
--- a/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter_unittest.cc
+++ b/components/safe_browsing/core/browser/safe_browsing_lookup_mechanism_experimenter_unittest.cc
@@ -291,7 +291,7 @@
         url_real_time_result, hash_database_result, hash_real_time_result);
   }
 
-  ExperimentThreatType GetExperimentDetailsThreatType(
+  absl::optional<ExperimentThreatType> GetExperimentDetailsThreatType(
       absl::optional<SBThreatType> threat_type) {
     return Experimenter::GetExperimentDetailsThreatType(threat_type);
   }
@@ -1047,24 +1047,38 @@
               urt_hpd_hprt_url_level_validation_details[0]
                   .value()
                   .matched_high_confidence_allowlist);
-    EXPECT_EQ(experiment_details.url_realtime_details()
-                  .locally_cached_results_threat_type(),
-              GetExperimentDetailsThreatType(
-                  urt_hpd_hprt_url_level_validation_details[0]
-                      .value()
-                      .locally_cached_results_threat_type));
+    if (urt_hpd_hprt_url_level_validation_details[0]
+            .value()
+            .locally_cached_results_threat_type.has_value()) {
+      EXPECT_EQ(experiment_details.url_realtime_details()
+                    .locally_cached_results_threat_type(),
+                GetExperimentDetailsThreatType(
+                    urt_hpd_hprt_url_level_validation_details[0]
+                        .value()
+                        .locally_cached_results_threat_type));
+    } else {
+      EXPECT_FALSE(experiment_details.url_realtime_details()
+                       .has_locally_cached_results_threat_type());
+    }
     EXPECT_EQ(experiment_details.hash_realtime_details().threat_type(),
               GetExperimentDetailsThreatType(urt_hpd_hprt_threat_types[2]));
     EXPECT_EQ(experiment_details.hash_realtime_details().matched_global_cache(),
               urt_hpd_hprt_url_level_validation_details[2]
                   .value()
                   .matched_high_confidence_allowlist);
-    EXPECT_EQ(experiment_details.hash_realtime_details()
-                  .locally_cached_results_threat_type(),
-              GetExperimentDetailsThreatType(
-                  urt_hpd_hprt_url_level_validation_details[2]
-                      .value()
-                      .locally_cached_results_threat_type));
+    if (urt_hpd_hprt_url_level_validation_details[2]
+            .value()
+            .locally_cached_results_threat_type.has_value()) {
+      EXPECT_EQ(experiment_details.hash_realtime_details()
+                    .locally_cached_results_threat_type(),
+                GetExperimentDetailsThreatType(
+                    urt_hpd_hprt_url_level_validation_details[2]
+                        .value()
+                        .locally_cached_results_threat_type));
+    } else {
+      EXPECT_FALSE(experiment_details.hash_realtime_details()
+                       .has_locally_cached_results_threat_type());
+    }
     ping_manager_->ClearReport();
   }
 
@@ -1170,7 +1184,7 @@
     std::vector<absl::optional<UrlLevelValidationDetails>>
         urt_hpd_hprt_url_level_validation_details = {
             UrlLevelValidationDetails(
-                /*locally_cached_results_threat_type=*/SB_THREAT_TYPE_BILLING,
+                /*locally_cached_results_threat_type=*/absl::nullopt,
                 /*real_time_request_failed=*/false,
                 /*matched_high_confidence_allowlist=*/true),
             UrlLevelValidationDetails(
@@ -1201,7 +1215,7 @@
                 /*real_time_request_failed=*/false,
                 /*matched_high_confidence_allowlist=*/absl::nullopt),
             UrlLevelValidationDetails(
-                /*locally_cached_results_threat_type=*/SB_THREAT_TYPE_SAFE,
+                /*locally_cached_results_threat_type=*/absl::nullopt,
                 /*real_time_request_failed=*/false,
                 /*matched_high_confidence_allowlist=*/true)};
     RunUrlLevelValidationTest(
@@ -1218,7 +1232,7 @@
             UrlLevelValidationDetails(
                 /*locally_cached_results_threat_type=*/SB_THREAT_TYPE_SAFE,
                 /*real_time_request_failed=*/false,
-                /*matched_high_confidence_allowlist=*/true),
+                /*matched_high_confidence_allowlist=*/false),
             UrlLevelValidationDetails(
                 /*locally_cached_results_threat_type=*/absl::nullopt,
                 /*real_time_request_failed=*/false,
@@ -1226,7 +1240,7 @@
             UrlLevelValidationDetails(
                 /*locally_cached_results_threat_type=*/SB_THREAT_TYPE_BILLING,
                 /*real_time_request_failed=*/false,
-                /*matched_high_confidence_allowlist=*/true)};
+                /*matched_high_confidence_allowlist=*/false)};
     RunUrlLevelValidationTest(
         urt_hpd_hprt_threat_types, urt_hpd_hprt_url_level_validation_details,
         /*urt_hpd_hprt_time_out=*/no_time_outs, /*expect_report_sent=*/true);
@@ -1346,10 +1360,9 @@
        TestGetExperimentDetailsThreatType) {
   struct TestCase {
     absl::optional<SBThreatType> threat_type;
-    ExperimentThreatType expected_experiment_threat_type;
+    absl::optional<ExperimentThreatType> expected_experiment_threat_type;
   } test_cases[] = {
-      {absl::nullopt, ClientSafeBrowsingReportRequest::
-                          HashRealTimeExperimentDetails::SAFE_OR_OTHER},
+      {absl::nullopt, absl::nullopt},
       {SB_THREAT_TYPE_URL_PHISHING,
        ClientSafeBrowsingReportRequest::HashRealTimeExperimentDetails::
            PHISHING},
diff --git a/components/viz/common/quads/aggregated_render_pass.h b/components/viz/common/quads/aggregated_render_pass.h
index 88032f7..31a8646 100644
--- a/components/viz/common/quads/aggregated_render_pass.h
+++ b/components/viz/common/quads/aggregated_render_pass.h
@@ -98,6 +98,13 @@
   // Indicates current RenderPass is a color conversion pass.
   bool is_color_conversion_pass = false;
 
+  // Windows only: Indicates that the render pass backing's updates need to be
+  // synchronized with tree updates. A swap chain does not synchronize its
+  // presents with DComp commits. This is needed when e.g. the render pass has
+  // video holes that need to line up with other overlays or is itself presented
+  // as an overlay.
+  bool needs_synchronous_dcomp_commit = false;
+
   void AsValueInto(base::trace_event::TracedValue* dict) const;
 
  private:
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index dd6a993..27c944a 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -571,7 +571,6 @@
     sources += [
       "display/overlay_dc_unittest.cc",
       "display_embedder/output_device_backing_unittest.cc",
-      "display_embedder/skia_output_device_dcomp_unittest.cc",
       "display_embedder/software_output_device_win_unittest.cc",
     ]
 
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
index f168bcff..60836b20 100644
--- a/components/viz/service/display/direct_renderer.cc
+++ b/components/viz/service/display/direct_renderer.cc
@@ -189,15 +189,10 @@
   base::flat_map<AggregatedRenderPassId, RenderPassRequirements>
       render_passes_in_frame;
   for (const auto& pass : render_passes_in_draw_order) {
-    if (pass == root_render_pass) {
-      // Requirements for the root render pass are communicated in
-      // AllocateRenderPassResourceIfNeeded().
-      continue;
-    }
-
     // If there's a copy request, we need an explicit renderpass backing so
     // only try to draw directly if there are no copy requests.
-    if (pass->copy_requests.empty()) {
+    bool is_root = pass == root_render_pass;
+    if (!is_root && pass->copy_requests.empty()) {
       if (const DrawQuad* quad = CanPassBeDrawnDirectly(pass.get())) {
         // If the render pass is drawn directly, it will not be drawn from as
         // a render pass so it's not added to the map.
@@ -205,12 +200,9 @@
         continue;
       }
     }
-    gfx::Size size = CalculateTextureSizeForRenderPass(pass.get());
-    auto color_space = RenderPassColorSpace(pass.get());
-    auto format = GetColorSpaceResourceFormat(color_space);
 
-    render_passes_in_frame[pass->id] = {size, pass->generate_mipmap, format,
-                                        color_space};
+    render_passes_in_frame[pass->id] =
+        CalculateRenderPassRequirements(pass.get());
   }
   UMA_HISTOGRAM_COUNTS_1000(
       "Compositing.Display.FlattenedRenderPassCount",
@@ -262,11 +254,6 @@
   current_frame()->device_viewport_size = device_viewport_size;
   current_frame()->display_color_spaces = display_color_spaces;
 
-  // DecideRenderPassAllocationsForFrame needs
-  // current_frame()->display_color_spaces to decide the color space
-  // of each render pass.
-  DecideRenderPassAllocationsForFrame(*render_passes_in_draw_order);
-
   output_surface_->SetNeedsMeasureNextDrawLatency();
   BeginDrawingFrame();
 
@@ -379,7 +366,7 @@
     // TODO(penghuang): verify this logic with SkiaRenderer.
     if (!output_surface_->capabilities().supports_surfaceless)
       needs_full_frame_redraw = true;
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#elif BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
     // If compositing is delegated, then there will be no output_surface_plane,
     // and we should not trigger a redraw of the root render pass.
     // Pixel tests will not be displayed as overlay planes, so they need redraw.
@@ -393,6 +380,13 @@
 #endif
   }
 
+  // DecideRenderPassAllocationsForFrame needs
+  // current_frame()->display_color_spaces to decide the color space
+  // of each render pass. Overlay processing is also allowed to modify the
+  // render pass backing requirements due to e.g. a underlay promotion. On
+  // Windows, the root render pass' size is based on the |reshape_params_|.
+  DecideRenderPassAllocationsForFrame(*render_passes_in_draw_order);
+
   // Draw all non-root render passes except for the root render pass.
   for (const auto& pass : *render_passes_in_draw_order) {
     if (pass.get() == root_render_pass)
@@ -772,6 +766,51 @@
   return false;
 }
 
+DirectRenderer::RenderPassRequirements
+DirectRenderer::CalculateRenderPassRequirements(
+    const AggregatedRenderPass* render_pass) const {
+  bool is_root = render_pass == current_frame()->root_render_pass;
+
+  RenderPassRequirements requirements;
+
+  if (is_root) {
+    requirements.size = surface_size_for_swap_buffers();
+    requirements.generate_mipmap = false;
+    requirements.color_space = reshape_color_space();
+    requirements.format = GetResourceFormat(reshape_buffer_format());
+
+    // All root render pass backings allocated by the renderer needs to
+    // eventually go into some composition tree. Other things that own/allocate
+    // the root pass backing include the output device and buffer queue.
+    requirements.is_scanout = true;
+
+#if BUILDFLAG(IS_WIN)
+    requirements.scanout_dcomp_surface =
+        render_pass->needs_synchronous_dcomp_commit;
+
+    // On Windows, the root render pass can be made transparent due to overlay
+    // processing promoting a quad as an underlay. If the format we picked does
+    // not have alpha bits, we ned to change to one that does.
+    if (render_pass->has_transparent_background &&
+        AlphaBits(requirements.format) == 0) {
+      requirements.format =
+          GetColorSpaceResourceFormat(requirements.color_space);
+    }
+#endif
+  } else {
+    requirements.size = CalculateTextureSizeForRenderPass(render_pass);
+    requirements.generate_mipmap = render_pass->generate_mipmap;
+    requirements.color_space = RenderPassColorSpace(render_pass);
+    requirements.format = GetColorSpaceResourceFormat(requirements.color_space);
+  }
+
+  if (render_pass->has_transparent_background) {
+    DCHECK_GT(AlphaBits(requirements.format), 0);
+  }
+
+  return requirements;
+}
+
 void DirectRenderer::UseRenderPass(const AggregatedRenderPass* render_pass) {
   bool is_root = render_pass == current_frame()->root_render_pass;
   current_frame()->current_render_pass = render_pass;
@@ -790,21 +829,13 @@
     return;
   }
 
-  RenderPassRequirements requirements;
-  if (is_root) {
-    requirements.size = surface_size_for_swap_buffers();
-    requirements.generate_mipmap = false;
-    requirements.color_space = reshape_color_space();
-    requirements.format = GetResourceFormat(reshape_buffer_format());
-  } else {
-    requirements.size = CalculateTextureSizeForRenderPass(render_pass);
+  DirectRenderer::RenderPassRequirements requirements =
+      CalculateRenderPassRequirements(render_pass);
+  // We should not change the buffer size for the root render pass.
+  if (!is_root) {
     requirements.size.Enlarge(enlarge_pass_texture_amount_.width(),
                               enlarge_pass_texture_amount_.height());
-    requirements.generate_mipmap = render_pass->generate_mipmap;
-    requirements.color_space = CurrentRenderPassColorSpace();
-    requirements.format = GetColorSpaceResourceFormat(requirements.color_space);
   }
-
   AllocateRenderPassResourceIfNeeded(render_pass->id, requirements);
 
   // TODO(crbug.com/582554): This change applies only when Vulkan is enabled and
@@ -936,7 +967,7 @@
 }
 
 gfx::Size DirectRenderer::CalculateTextureSizeForRenderPass(
-    const AggregatedRenderPass* render_pass) {
+    const AggregatedRenderPass* render_pass) const {
   // Round the size of the render pass backings to a multiple of 64 pixels. This
   // reduces memory fragmentation. https://crbug.com/146070. This also allows
   // backings to be more easily reused during a resize operation.
diff --git a/components/viz/service/display/direct_renderer.h b/components/viz/service/display/direct_renderer.h
index a3f31a0..b4da9288 100644
--- a/components/viz/service/display/direct_renderer.h
+++ b/components/viz/service/display/direct_renderer.h
@@ -204,6 +204,11 @@
     bool generate_mipmap = false;
     ResourceFormat format;
     gfx::ColorSpace color_space;
+    // Render pass wants scanout
+    bool is_scanout = false;
+    // Render pass wants to synchronize updates with other overlays, on Windows
+    // this means a DComp surface.
+    bool scanout_dcomp_surface = false;
   };
 
   static gfx::RectF QuadVertexRect();
@@ -228,9 +233,11 @@
   void SetScissorTestRectInDrawSpace(const gfx::Rect& draw_space_rect);
 
   gfx::Size CalculateTextureSizeForRenderPass(
-      const AggregatedRenderPass* render_pass);
+      const AggregatedRenderPass* render_pass) const;
   gfx::Size CalculateSizeForOutputSurface(
       const gfx::Size& device_viewport_size);
+  RenderPassRequirements CalculateRenderPassRequirements(
+      const AggregatedRenderPass* render_pass) const;
 
   void FlushPolygons(
       base::circular_deque<std::unique_ptr<DrawPolygon>>* poly_list,
diff --git a/components/viz/service/display/overlay_dc_unittest.cc b/components/viz/service/display/overlay_dc_unittest.cc
index fdd13b25..70cad658 100644
--- a/components/viz/service/display/overlay_dc_unittest.cc
+++ b/components/viz/service/display/overlay_dc_unittest.cc
@@ -196,14 +196,34 @@
   return SkM44();
 }
 
-class DCLayerOverlayTest : public testing::Test {
+class DCLayerOverlayTest : public testing::Test,
+                           public testing::WithParamInterface<bool> {
+ public:
+  bool IsUsingDCompPresenter() const { return GetParam(); }
+
+  static const char* GetParamName(
+      const testing::TestParamInfo<ParamType>& info) {
+    return info.param ? "DCompPresenter" : "DirectCompositionChildSurfaceWin";
+  }
+
  protected:
   DCLayerOverlayTest() {
+    std::vector<base::test::FeatureRef> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
     // With DisableVideoOverlayIfMoving, videos are required to be stable for a
     // certain number of frames to be considered for overlay promotion. This
     // complicates tests since it adds behavior dependent on the number of times
     // |Process| is called.
-    feature_list_.InitAndDisableFeature(features::kDisableVideoOverlayIfMoving);
+    disabled_features.push_back(features::kDisableVideoOverlayIfMoving);
+
+    if (IsUsingDCompPresenter()) {
+      enabled_features.push_back(features::kDCompPresenter);
+    } else {
+      disabled_features.push_back(features::kDCompPresenter);
+    }
+
+    feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
   void SetUp() override {
@@ -225,6 +245,11 @@
     overlay_processor_
         ->set_frames_since_last_qualified_multi_overlays_for_testing(5);
     EXPECT_TRUE(overlay_processor_->IsOverlaySupported());
+
+    if (IsUsingDCompPresenter()) {
+      output_surface_plane_ =
+          OverlayProcessorInterface::OutputSurfaceOverlayPlane();
+    }
   }
 
   void TearDown() override {
@@ -237,10 +262,23 @@
     output_surface_ = nullptr;
   }
 
+  OverlayProcessorInterface::OutputSurfaceOverlayPlane*
+  GetOutputSurfacePlane() {
+    if (IsUsingDCompPresenter()) {
+      EXPECT_TRUE(output_surface_plane_.has_value());
+      return &output_surface_plane_.value();
+    } else {
+      EXPECT_FALSE(output_surface_plane_.has_value());
+      return nullptr;
+    }
+  }
+
   void TestRenderPassRootTransform(bool is_overlay);
 
   base::test::ScopedFeatureList feature_list_;
   std::unique_ptr<MockDCLayerOutputSurface> output_surface_;
+  absl::optional<OverlayProcessorInterface::OutputSurfaceOverlayPlane>
+      output_surface_plane_;
   cc::FakeOutputSurfaceClient output_surface_client_;
   std::unique_ptr<DisplayResourceProviderSkia> resource_provider_;
   absl::optional<DisplayResourceProviderSkia::LockSetForExternalUse>
@@ -252,7 +290,7 @@
   std::vector<gfx::Rect> content_bounds_;
 };
 
-TEST_F(DCLayerOverlayTest, DisableVideoOverlayIfMovingFeature) {
+TEST_P(DCLayerOverlayTest, DisableVideoOverlayIfMovingFeature) {
   auto ProcessForOverlaysSingleVideoRectWithOffset =
       [&](gfx::Vector2d video_rect_offset) {
         auto pass = CreateRenderPass();
@@ -273,8 +311,9 @@
 
         overlay_processor_->ProcessForOverlays(
             resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
-            render_pass_filters, render_pass_backdrop_filters, {}, nullptr,
-            &dc_layer_list, &damage_rect_, &content_bounds_);
+            render_pass_filters, render_pass_backdrop_filters, {},
+            GetOutputSurfacePlane(), &dc_layer_list, &damage_rect_,
+            &content_bounds_);
 
         return dc_layer_list;
       };
@@ -307,7 +346,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, Occluded) {
+TEST_P(DCLayerOverlayTest, Occluded) {
   {
     auto pass = CreateRenderPass();
     SharedQuadState* first_shared_state = pass->shared_quad_state_list.back();
@@ -350,8 +389,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     EXPECT_EQ(2U, dc_layer_list.size());
     EXPECT_EQ(-1, dc_layer_list.front().plane_z_order);
@@ -400,8 +439,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     EXPECT_EQ(2U, dc_layer_list.size());
     EXPECT_EQ(-1, dc_layer_list.front().plane_z_order);
@@ -414,7 +453,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, DamageRectWithoutVideoDamage) {
+TEST_P(DCLayerOverlayTest, DamageRectWithoutVideoDamage) {
   {
     auto pass = CreateRenderPass();
     SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
@@ -451,8 +490,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(-1, dc_layer_list.back().plane_z_order);
     // All rects must be redrawn at the first frame.
@@ -494,8 +533,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(-1, dc_layer_list.back().plane_z_order);
     // Only the non-overlay damaged rect need to be drawn by the gl compositor
@@ -503,7 +542,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, DamageRect) {
+TEST_P(DCLayerOverlayTest, DamageRect) {
   for (int i = 0; i < 2; i++) {
     auto pass = CreateRenderPass();
     SharedQuadState* shared_quad_state = pass->shared_quad_state_list.back();
@@ -523,8 +562,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(1, dc_layer_list.back().plane_z_order);
     // Damage rect should be unchanged on initial frame because of resize, but
@@ -537,7 +576,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, ClipRect) {
+TEST_P(DCLayerOverlayTest, ClipRect) {
   // Process twice. The second time through the overlay list shouldn't change,
   // which will allow the damage rect to reflect just the changes in that
   // frame.
@@ -570,8 +609,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     // Because of clip rects the overlay isn't occluded and shouldn't be an
     // underlay.
@@ -585,7 +624,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, TransparentOnTop) {
+TEST_P(DCLayerOverlayTest, TransparentOnTop) {
   // Process twice. The second time through the overlay list shouldn't change,
   // which will allow the damage rect to reflect just the changes in that
   // frame.
@@ -608,8 +647,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(1, dc_layer_list.back().plane_z_order);
     // Quad isn't opaque, so underlying damage must remain the same.
@@ -617,7 +656,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, UnderlayDamageRectWithQuadOnTopUnchanged) {
+TEST_P(DCLayerOverlayTest, UnderlayDamageRectWithQuadOnTopUnchanged) {
   for (int i = 0; i < 3; i++) {
     auto pass = CreateRenderPass();
     // Add a solid color quad on top
@@ -649,8 +688,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(-1, dc_layer_list.back().plane_z_order);
     // Damage rect should be unchanged on initial frame, but should be reduced
@@ -665,7 +704,7 @@
 }
 
 // Test whether quads with rounded corners are supported.
-TEST_F(DCLayerOverlayTest, RoundedCorners) {
+TEST_P(DCLayerOverlayTest, RoundedCorners) {
   // Frame #0
   {
     auto pass = CreateRenderPass();
@@ -694,8 +733,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     auto* root_pass = pass_list.back().get();
     auto* replaced_quad = root_pass->quad_list.back();
@@ -752,8 +791,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     auto* root_pass = pass_list.back().get();
     auto* replaced_quad = root_pass->quad_list.back();
@@ -810,8 +849,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     auto* root_pass = pass_list.back().get();
     auto* replaced_quad = root_pass->quad_list.back();
@@ -839,7 +878,7 @@
 
 // If there are multiple yuv overlay quad candidates, no overlay will be
 // promoted to save power.
-TEST_F(DCLayerOverlayTest, MultipleYUVOverlays) {
+TEST_P(DCLayerOverlayTest, MultipleYUVOverlays) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
       features::kNoUndamagedOverlayPromotion);
@@ -879,8 +918,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     // Skip overlays.
     EXPECT_EQ(0U, dc_layer_list.size());
@@ -894,7 +933,7 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, SetEnableDCLayers) {
+TEST_P(DCLayerOverlayTest, SetEnableDCLayers) {
   // Start without DC layers.
   overlay_processor_->set_using_dc_layers_for_testing(false);
 
@@ -919,16 +958,28 @@
     const gfx::Rect expected_damage =
         (i == 0) ? pass_list.back()->output_rect : gfx::Rect();
 
-    if (i == 0)
-      EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(true)).Times(1);
-    else
+    if (IsUsingDCompPresenter()) {
       EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0);
+    } else {
+      if (i == 0) {
+        EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(true)).Times(1);
+      } else {
+        EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0);
+      }
+    }
 
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
+
+    if (IsUsingDCompPresenter() && i == 0) {
+      EXPECT_TRUE(pass_list.back()->needs_synchronous_dcomp_commit);
+      EXPECT_TRUE(pass_list.back()->has_transparent_background);
+      ASSERT_TRUE(output_surface_plane_.has_value());
+      EXPECT_TRUE(output_surface_plane_->enable_blending);
+    }
 
     EXPECT_EQ(1U, dc_layer_list.size());
     EXPECT_EQ(1, dc_layer_list.back().plane_z_order);
@@ -963,16 +1014,28 @@
                                           ? pass_list.back()->output_rect
                                           : damage_rect_;
 
-    if (i + 1 == 60)
-      EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(false)).Times(1);
-    else
+    if (IsUsingDCompPresenter()) {
       EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0);
+    } else {
+      if (i + 1 == 60) {
+        EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(false)).Times(1);
+      } else {
+        EXPECT_CALL(*output_surface_.get(), SetEnableDCLayers(_)).Times(0);
+      }
+    }
 
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
+
+    if (IsUsingDCompPresenter() && i + 1 == 60) {
+      EXPECT_FALSE(pass_list.back()->needs_synchronous_dcomp_commit);
+      EXPECT_FALSE(pass_list.back()->has_transparent_background);
+      ASSERT_TRUE(output_surface_plane_.has_value());
+      EXPECT_FALSE(output_surface_plane_->enable_blending);
+    }
 
     EXPECT_EQ(0u, dc_layer_list.size());
     EXPECT_EQ(damage_rect_, expected_damage);
@@ -983,7 +1046,7 @@
 
 // Test that the video is forced to underlay if the expanded quad of pixel
 // moving foreground filter is on top.
-TEST_F(DCLayerOverlayTest, PixelMovingForegroundFilter) {
+TEST_P(DCLayerOverlayTest, PixelMovingForegroundFilter) {
   AggregatedRenderPassList pass_list;
 
   // Create a non-root render pass with a pixel-moving foreground filter.
@@ -1042,8 +1105,8 @@
   overlay_processor_->ProcessForOverlays(
       resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
       render_pass_filters, render_pass_backdrop_filters,
-      std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-      &damage_rect_, &content_bounds_);
+      std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+      &dc_layer_list, &damage_rect_, &content_bounds_);
 
   EXPECT_EQ(1U, dc_layer_list.size());
   // Make sure the video is in an underlay mode if the overlay quad intersects
@@ -1053,7 +1116,7 @@
 }
 
 // Test that the video is not promoted if a quad on top has backdrop filters.
-TEST_F(DCLayerOverlayTest, BackdropFilter) {
+TEST_P(DCLayerOverlayTest, BackdropFilter) {
   AggregatedRenderPassList pass_list;
 
   // Create a non-root render pass with a backdrop filter.
@@ -1114,8 +1177,8 @@
   overlay_processor_->ProcessForOverlays(
       resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
       render_pass_filters, render_pass_backdrop_filters,
-      std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-      &damage_rect_, &content_bounds_);
+      std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+      &dc_layer_list, &damage_rect_, &content_bounds_);
 
   // Make sure the video is not promoted if the overlay quad intersects
   // with the backdrop filter rpdq->rect.
@@ -1124,7 +1187,7 @@
 }
 
 // Test if overlay is not used when video capture is on.
-TEST_F(DCLayerOverlayTest, VideoCapture) {
+TEST_P(DCLayerOverlayTest, VideoCapture) {
   // Frame #0
   {
     auto pass = CreateRenderPass();
@@ -1156,8 +1219,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     // Use overlay for the video quad.
     EXPECT_EQ(1U, dc_layer_list.size());
@@ -1195,8 +1258,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
 
     // Should not use overlay for the video when video capture is on.
     EXPECT_EQ(0U, dc_layer_list.size());
@@ -1209,11 +1272,11 @@
   }
 }
 
-TEST_F(DCLayerOverlayTest, RenderPassRootTransformOverlay) {
+TEST_P(DCLayerOverlayTest, RenderPassRootTransformOverlay) {
   TestRenderPassRootTransform(/*is_overlay*/ true);
 }
 
-TEST_F(DCLayerOverlayTest, RenderPassRootTransformUnderlay) {
+TEST_P(DCLayerOverlayTest, RenderPassRootTransformUnderlay) {
   TestRenderPassRootTransform(/*is_overlay*/ false);
 }
 
@@ -1261,8 +1324,8 @@
     overlay_processor_->ProcessForOverlays(
         resource_provider_.get(), &pass_list, GetIdentityColorMatrix(),
         render_pass_filters, render_pass_backdrop_filters,
-        std::move(surface_damage_rect_list), nullptr, &dc_layer_list,
-        &damage_rect_, &content_bounds_);
+        std::move(surface_damage_rect_list), GetOutputSurfacePlane(),
+        &dc_layer_list, &damage_rect_, &content_bounds_);
     LOG(INFO) << damage_rect_.ToString();
 
     EXPECT_EQ(dc_layer_list.size(), 1u);
@@ -1292,5 +1355,10 @@
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         DCLayerOverlayTest,
+                         testing::Bool(),
+                         &DCLayerOverlayTest::GetParamName);
+
 }  // namespace
 }  // namespace viz
diff --git a/components/viz/service/display/overlay_processor_win.cc b/components/viz/service/display/overlay_processor_win.cc
index 22393798..08a799e 100644
--- a/components/viz/service/display/overlay_processor_win.cc
+++ b/components/viz/service/display/overlay_processor_win.cc
@@ -7,6 +7,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "components/viz/common/display/renderer_settings.h"
@@ -14,6 +15,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/service/display/display_resource_provider.h"
 #include "components/viz/service/display/output_surface.h"
+#include "gpu/config/gpu_finch_features.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gl/gl_utils.h"
 
@@ -92,13 +94,42 @@
   }
 
   if (was_using_dc_layers != using_dc_layers_) {
-    output_surface_->SetEnableDCLayers(using_dc_layers_);
     // The entire surface has to be redrawn if switching from or to direct
     // composition layers, because the previous contents are discarded and some
     // contents would otherwise be undefined.
     *damage_rect = root_render_pass->output_rect;
   }
 
+  if (base::FeatureList::IsEnabled(features::kDCompPresenter)) {
+    if (!root_render_pass->copy_requests.empty()) {
+      // A DComp surface is not readable by viz.
+      // |DCLayerOverlayProcessor::Process| should avoid overlay candidates if
+      // there are e.g. copy output requests present.
+      DCHECK(!using_dc_layers_);
+    }
+
+    // We have overlays, so our root surface requires a backing that
+    // synchronizes with DComp commit. A swap chain's Present does not
+    // synchronize with the DComp tree updates and would result in minor desync
+    // during e.g. scrolling videos.
+    root_render_pass->needs_synchronous_dcomp_commit = using_dc_layers_;
+
+    // We only need to have a transparent backing if there's underlays, but we
+    // unconditionally ask for transparency to avoid thrashing allocations if a
+    // video alternated between overlay and underlay.
+    root_render_pass->has_transparent_background = using_dc_layers_;
+
+    // |root_render_pass| will be promoted to overlay only if
+    // |output_surface_plane| is present.
+    DCHECK_NE(output_surface_plane, nullptr);
+    output_surface_plane->enable_blending =
+        root_render_pass->has_transparent_background;
+  } else {
+    if (was_using_dc_layers != using_dc_layers_) {
+      output_surface_->SetEnableDCLayers(using_dc_layers_);
+    }
+  }
+
   if (debug_settings_->show_dc_layer_debug_borders) {
     InsertDebugBorderDrawQuadsForOverlayCandidates(
         *candidates, root_render_pass, *damage_rect);
diff --git a/components/viz/service/display/skia_output_surface.h b/components/viz/service/display/skia_output_surface.h
index be50d60..3223eb70 100644
--- a/components/viz/service/display/skia_output_surface.h
+++ b/components/viz/service/display/skia_output_surface.h
@@ -108,6 +108,7 @@
                                          const gfx::Size& size,
                                          ResourceFormat format,
                                          bool mipmap,
+                                         bool scanout_dcomp_surface,
                                          sk_sp<SkColorSpace> color_space,
                                          bool is_overlay,
                                          const gpu::Mailbox& mailbox) = 0;
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
index 802d135f..2647cc4 100644
--- a/components/viz/service/display/skia_renderer.cc
+++ b/components/viz/service/display/skia_renderer.cc
@@ -823,6 +823,11 @@
       current_gpu_commands_completed_fence_.get());
   this->resource_provider()->SetReleaseFence(current_release_fence_.get());
 
+#if BUILDFLAG(IS_WIN)
+  // Windows does not use buffer queue because a swap chain and DComp surface
+  // internally manage buffers and cross-frame damage. It instead lets the
+  // renderer allocate the root surface like a normal render pass backing.
+#else
   if (output_surface->capabilities().renderer_allocates_images) {
     // When using dynamic frame buffer allocation we'll start with 0 buffers and
     // let EnsureMinNumberOfBuffers() increase it later.
@@ -834,6 +839,7 @@
         skia_output_surface_, skia_output_surface_->GetSurfaceHandle(),
         number_of_buffers);
   }
+#endif
 
 #if OS_ANDROID
   use_real_color_space_for_stream_video_ =
@@ -868,13 +874,9 @@
   if (current_frame()->output_surface_plane) {
     auto& surface_plane = current_frame()->output_surface_plane.value();
 
-    if (!buffer_queue_) {
+    if (!output_surface_->capabilities().renderer_allocates_images) {
       skia_output_surface_->ScheduleOutputSurfaceAsOverlay(surface_plane);
     } else {
-#if BUILDFLAG(IS_WIN)
-      // Windows does not use buffer_queue_ so this won't be reached.
-      NOTREACHED();
-#else
       auto root_pass_backing =
           render_pass_backings_.find(current_frame()->root_render_pass->id);
       // The root pass backing should always exist.
@@ -883,11 +885,11 @@
       OverlayCandidate surface_candidate;
       surface_candidate.mailbox = root_pass_backing->second.mailbox;
       surface_candidate.is_root_render_pass = true;
-#if BUILDFLAG(IS_APPLE)
+#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_WIN)
       surface_candidate.transform = gfx::Transform();
 #else
       surface_candidate.transform = surface_plane.transform;
-#endif  // BUILDFLAG(IS_APPLE)
+#endif
       surface_candidate.display_rect = surface_plane.display_rect;
       surface_candidate.uv_rect = surface_plane.uv_rect;
       surface_candidate.resource_size_in_pixels = surface_plane.resource_size;
@@ -903,7 +905,6 @@
 
       current_frame()->overlay_list.insert(
           current_frame()->overlay_list.begin(), surface_candidate);
-#endif  // BUILDFLAG(IS_WIN)
     }
   } else {
 #if BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_APPLE)
@@ -1126,11 +1127,11 @@
   RenderPassBacking& backing = iter->second;
   current_canvas_ = skia_output_surface_->BeginPaintRenderPass(
       render_pass_id, backing.size, backing.format, backing.generate_mipmap,
-      RenderPassBackingSkColorSpace(backing), /*is_overlay=*/is_root,
-      backing.mailbox);
+      backing.scanout_dcomp_surface, RenderPassBackingSkColorSpace(backing),
+      /*is_overlay=*/is_root, backing.mailbox);
 
   if (is_root && debug_settings_->show_overdraw_feedback) {
-    DCHECK(buffer_queue_);
+    DCHECK(output_surface_->capabilities().renderer_allocates_images);
     current_canvas_ = skia_output_surface_->RecordOverdrawForCurrentPaint();
   }
 }
@@ -3072,7 +3073,9 @@
   current_canvas_ = nullptr;
   // Non-root render passes that are scheduled as overlays will be painted in
   // PrepareRenderPassOverlay().
-  bool is_overlay = buffer_queue_ && is_root_render_pass;
+  bool is_overlay =
+      skia_output_surface_->capabilities().renderer_allocates_images &&
+      is_root_render_pass;
   EndPaint(current_render_pass_update_rect_, /*failed=*/false, is_overlay);
 
   // Defer flushing drawing task for root render pass, to avoid extra
@@ -3097,15 +3100,19 @@
   const auto& root_pass_id = render_passes_in_draw_order.back()->id;
   std::vector<AggregatedRenderPassId> passes_to_delete;
   for (const auto& [backing_id, backing] : render_pass_backings_) {
-    // If a root backing exists but its id does not match the current root
-    // render pass id, then it must be an old backing that should be deleted.
-    // Otherwise we should not delete a root backing in case it is scheduled
-    // this frame but not drawn.
-    if (backing.is_root) {
-      if (backing_id != root_pass_id) {
-        passes_to_delete.push_back(backing_id);
+    // Buffer queue's root manages the root pass backing and its bookkeeping
+    // separately from other render pass backings.
+    if (buffer_queue_) {
+      // If a root backing exists but its id does not match the current root
+      // render pass id, then it must be an old backing that should be deleted.
+      // Otherwise we should not delete a root backing in case it is scheduled
+      // this frame but not drawn (e.g. in the case of overlay-only damage).
+      if (backing.is_root) {
+        if (backing_id != root_pass_id) {
+          passes_to_delete.push_back(backing_id);
+        }
+        continue;
       }
-      continue;
     }
 
     auto render_pass_it = render_passes_in_frame.find(backing_id);
@@ -3122,9 +3129,12 @@
     bool no_change_in_format = requirements.format == backing.format;
     bool no_change_in_color_space =
         requirements.color_space == backing.color_space;
+    bool scanout_appropriate =
+        requirements.is_scanout == backing.is_scanout &&
+        requirements.scanout_dcomp_surface == backing.scanout_dcomp_surface;
 
     if (!size_appropriate || !mipmap_appropriate || !no_change_in_format ||
-        !no_change_in_color_space) {
+        !no_change_in_color_space || !scanout_appropriate) {
       passes_to_delete.push_back(backing_id);
     }
   }
@@ -3134,10 +3144,10 @@
   for (size_t i = 0; i < passes_to_delete.size(); ++i) {
     auto it = render_pass_backings_.find(passes_to_delete[i]);
     auto& backing = it->second;
-    // Buffers for root render pass backings are managed by |buffer_queue_|, not
+    // Root render pass backings managed by |buffer_queue_| are not managed by
     // DisplayResourceProvider, so we should not destroy them here. This
     // reallocation is done in Reshape before drawing the frame
-    if (!backing.is_root) {
+    if (!(buffer_queue_ && backing.is_root)) {
       skia_output_surface_->DestroySharedImage(backing.mailbox);
     }
     render_pass_backings_.erase(it);
@@ -3151,8 +3161,11 @@
 void SkiaRenderer::AllocateRenderPassResourceIfNeeded(
     const AggregatedRenderPassId& render_pass_id,
     const RenderPassRequirements& requirements) {
-  if (render_pass_id == current_frame()->root_render_pass->id) {
-    DCHECK(buffer_queue_);
+  const bool is_root = render_pass_id == current_frame()->root_render_pass->id;
+
+  // Root render pass backings managed by |buffer_queue_| are not managed by
+  // DisplayResourceProvider, so we should not allocate them here.
+  if (buffer_queue_ && is_root) {
     auto& root_pass_backing = render_pass_backings_[render_pass_id];
     root_pass_backing.is_root = true;
     root_pass_backing.mailbox = buffer_queue_->GetCurrentBuffer();
@@ -3160,6 +3173,8 @@
     root_pass_backing.size = requirements.size;
     root_pass_backing.format = requirements.format;
     root_pass_backing.color_space = requirements.color_space;
+    root_pass_backing.is_scanout = true;
+    root_pass_backing.scanout_dcomp_surface = false;
     return;
   }
 
@@ -3169,14 +3184,43 @@
     // A root backing should not be used for other render passes. If the root
     // pass id has changed, then it's old backing should have been deleted
     // already in UpdateRenderPassTextures().
-    DCHECK(!it->second.is_root);
+    DCHECK(!(buffer_queue_ && it->second.is_root));
     return;
   }
 
   uint32_t usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
                    gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE;
-  if (requirements.generate_mipmap)
+  if (requirements.generate_mipmap) {
+    DCHECK(!requirements.is_scanout);
     usage |= gpu::SHARED_IMAGE_USAGE_MIPMAP;
+  }
+  if (requirements.is_scanout) {
+    usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
+
+#if BUILDFLAG(IS_WIN)
+    // DComp surfaces do not support RGB10A2 so we must fall back to swap
+    // chains. If this happens with video overlays, this can result in the video
+    // overlay and its parent surface having unsynchronized updates.
+    //
+    // TODO(tangm): We should clean this up by either avoiding HDR or using
+    //              RGBAF16 surfaces in this case.
+    const bool dcomp_surface_unsupported_format =
+        requirements.format == ResourceFormat::RGBA_1010102;
+
+    if (requirements.scanout_dcomp_surface &&
+        !dcomp_surface_unsupported_format) {
+      usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE;
+
+      // DComp surfaces are write-only, viz cannot sample them.
+      usage &= ~gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
+    }
+#else
+    DCHECK(!requirements.scanout_dcomp_surface);
+#endif
+  } else {
+    DCHECK(!requirements.scanout_dcomp_surface);
+  }
+
   auto mailbox = skia_output_surface_->CreateSharedImage(
       requirements.format, requirements.size, requirements.color_space, usage,
       gpu::kNullSurfaceHandle);
@@ -3184,7 +3228,8 @@
       render_pass_id,
       RenderPassBacking({requirements.size, requirements.generate_mipmap,
                          requirements.color_space, requirements.format, mailbox,
-                         /*is_root=*/false}));
+                         is_root, requirements.is_scanout,
+                         requirements.scanout_dcomp_surface}));
 }
 
 void SkiaRenderer::FlushOutputSurface() {
@@ -3302,9 +3347,14 @@
     auto mailbox = skia_output_surface_->CreateSharedImage(
         buffer_format, buffer_size, color_space, kOverlayUsage,
         gpu::kNullSurfaceHandle);
-    overlay_params.render_pass_backing = {
-        buffer_size, /*generate_mipmap=*/false, color_space, buffer_format,
-        mailbox,     /*is_root=*/false};
+    overlay_params.render_pass_backing = {buffer_size,
+                                          /*generate_mipmap=*/false,
+                                          color_space,
+                                          buffer_format,
+                                          mailbox,
+                                          /*is_root=*/false,
+                                          /*is_scanout=*/true,
+                                          /*scanout_dcomp_surface=*/false};
   } else {
     overlay_params = *it;
     available_render_pass_overlay_backings_.erase(it);
@@ -3482,6 +3532,7 @@
     current_canvas_ = skia_output_surface_->BeginPaintRenderPass(
         quad->render_pass_id, dst_overlay_backing.size,
         dst_overlay_backing.format, /*mipmap=*/false,
+        /*scanout_dcomp_surface=*/false,
         RenderPassBackingSkColorSpace(dst_overlay_backing),
         /*is_overlay=*/true, overlay->mailbox);
     if (!current_canvas_) {
diff --git a/components/viz/service/display/skia_renderer.h b/components/viz/service/display/skia_renderer.h
index 3653911c..61e49d8 100644
--- a/components/viz/service/display/skia_renderer.h
+++ b/components/viz/service/display/skia_renderer.h
@@ -276,11 +276,13 @@
 
   struct RenderPassBacking {
     gfx::Size size;
-    bool generate_mipmap;
+    bool generate_mipmap = false;
     gfx::ColorSpace color_space;
     ResourceFormat format;
     gpu::Mailbox mailbox;
-    bool is_root;
+    bool is_root = false;
+    bool is_scanout = false;
+    bool scanout_dcomp_surface = false;
   };
 
 #if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_OZONE)
diff --git a/components/viz/service/display_embedder/output_presenter_fuchsia.cc b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
index d00bab3..6f07a364 100644
--- a/components/viz/service/display_embedder/output_presenter_fuchsia.cc
+++ b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/feature_list.h"
+#include "base/notreached.h"
 #include "components/viz/common/features.h"
 #include "components/viz/common/gpu/vulkan_context_provider.h"
 #include "components/viz/common/resources/resource_format_utils.h"
@@ -23,106 +24,6 @@
 
 namespace viz {
 
-namespace {
-
-class PresenterImageFuchsia : public OutputPresenter::Image {
- public:
-  PresenterImageFuchsia(
-      gpu::SharedImageFactory* factory,
-      gpu::SharedImageRepresentationFactory* representation_factory,
-      SkiaOutputSurfaceDependency* deps)
-      : Image(factory, representation_factory, deps) {}
-  ~PresenterImageFuchsia() override;
-
-  // OutputPresenter::Image implementation.
-  void BeginPresent() final;
-  void EndPresent(gfx::GpuFenceHandle release_fence) final;
-  int GetPresentCount() const final;
-  void OnContextLost() final;
-
-  // Must only be called in between BeginPresent() and EndPresent().
-  scoped_refptr<gfx::NativePixmap> GetNativePixmap() {
-    DCHECK(scoped_overlay_read_access_);
-    return scoped_overlay_read_access_->GetNativePixmap();
-  }
-
-  // Must be called after BeginPresent() to get fences for frame.
-  void TakePresentationFences(
-      std::vector<gfx::GpuFenceHandle>& read_begin_fences,
-      std::vector<gfx::GpuFenceHandle>& read_end_fences);
-
- private:
-  std::vector<gfx::GpuFenceHandle> read_begin_fences_;
-  gfx::GpuFenceHandle read_end_fence_;
-};
-
-PresenterImageFuchsia::~PresenterImageFuchsia() {
-  DCHECK(read_begin_fences_.empty());
-  DCHECK(read_end_fence_.is_null());
-}
-
-void PresenterImageFuchsia::BeginPresent() {
-  DCHECK(read_end_fence_.is_null());
-
-  ++present_count_;
-
-  if (present_count_ == 1) {
-    DCHECK(!scoped_overlay_read_access_);
-    DCHECK(read_begin_fences_.empty());
-
-    scoped_overlay_read_access_ =
-        overlay_representation_->BeginScopedReadAccess();
-    DCHECK(scoped_overlay_read_access_);
-
-    // Take ownership of acquire fence.
-    gfx::GpuFenceHandle gpu_fence_handle =
-        scoped_overlay_read_access_->TakeAcquireFence();
-    if (!gpu_fence_handle.is_null())
-      read_begin_fences_.push_back(std::move(gpu_fence_handle));
-  }
-
-  // A new release fence is generated for each present. The fence for the last
-  // present gets waited on before giving up read access to the shared image.
-  gpu::ExternalSemaphore semaphore =
-      gpu::ExternalSemaphore::Create(deps_->GetVulkanContextProvider());
-  DCHECK(semaphore.is_valid());
-  read_end_fence_ = semaphore.TakeSemaphoreHandle().ToGpuFenceHandle();
-
-  scoped_overlay_read_access_->SetReleaseFence(read_end_fence_.Clone());
-}
-
-void PresenterImageFuchsia::EndPresent(gfx::GpuFenceHandle release_fence) {
-  DCHECK(present_count_);
-  DCHECK(release_fence.is_null());
-  --present_count_;
-  if (!present_count_) {
-    DCHECK(scoped_overlay_read_access_);
-    scoped_overlay_read_access_.reset();
-  }
-}
-
-int PresenterImageFuchsia::GetPresentCount() const {
-  return present_count_;
-}
-
-void PresenterImageFuchsia::OnContextLost() {
-  if (overlay_representation_)
-    overlay_representation_->OnContextLost();
-}
-
-void PresenterImageFuchsia::TakePresentationFences(
-    std::vector<gfx::GpuFenceHandle>& read_begin_fences,
-    std::vector<gfx::GpuFenceHandle>& read_end_fences) {
-  DCHECK(read_begin_fences.empty());
-  std::swap(read_begin_fences, read_begin_fences_);
-
-  DCHECK(read_end_fences.empty());
-  DCHECK(!read_end_fence_.is_null());
-  read_end_fences.push_back(std::move(read_end_fence_));
-}
-
-}  // namespace
-
 OutputPresenterFuchsia::PendingFrame::PendingFrame() = default;
 OutputPresenterFuchsia::PendingFrame::~PendingFrame() = default;
 
@@ -133,28 +34,21 @@
 // static
 std::unique_ptr<OutputPresenterFuchsia> OutputPresenterFuchsia::Create(
     ui::PlatformWindowSurface* window_surface,
-    SkiaOutputSurfaceDependency* deps,
-    gpu::SharedImageFactory* shared_image_factory,
-    gpu::SharedImageRepresentationFactory* representation_factory) {
+    SkiaOutputSurfaceDependency* deps) {
   if (!base::FeatureList::IsEnabled(
           features::kUseSkiaOutputDeviceBufferQueue)) {
     return {};
   }
 
-  return std::make_unique<OutputPresenterFuchsia>(
-      window_surface, deps, shared_image_factory, representation_factory);
+  return std::make_unique<OutputPresenterFuchsia>(window_surface, deps);
 }
 
 OutputPresenterFuchsia::OutputPresenterFuchsia(
     ui::PlatformWindowSurface* window_surface,
-    SkiaOutputSurfaceDependency* deps,
-    gpu::SharedImageFactory* shared_image_factory,
-    gpu::SharedImageRepresentationFactory* representation_factory)
-    : window_surface_(window_surface),
-      dependency_(deps),
-      shared_image_factory_(shared_image_factory),
-      shared_image_representation_factory_(representation_factory) {
-  DCHECK(window_surface_);
+    SkiaOutputSurfaceDependency* deps)
+    : window_surface_(window_surface), dependency_(deps) {
+  CHECK(window_surface_);
+  CHECK(features::ShouldRendererAllocateImages());
 }
 
 OutputPresenterFuchsia::~OutputPresenterFuchsia() = default;
@@ -177,7 +71,6 @@
     const gfx::ColorSpace& color_space,
     float device_scale_factor,
     gfx::OverlayTransform transform) {
-  frame_size_ = gfx::SkISizeToSize(characterization.dimensions());
   return true;
 }
 
@@ -185,63 +78,31 @@
 OutputPresenterFuchsia::AllocateImages(gfx::ColorSpace color_space,
                                        gfx::Size image_size,
                                        size_t num_images) {
-  DCHECK(!features::ShouldRendererAllocateImages());
-
-  // Fuchsia allocates images in batches and does not support allocating and
-  // releasing images on demand.
-  CHECK_NE(num_images, 1u);
-
-  // Create PresenterImageFuchsia for each buffer in the collection.
-  constexpr uint32_t image_usage = gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
-                                   gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
-                                   gpu::SHARED_IMAGE_USAGE_SCANOUT;
-
-  std::vector<std::unique_ptr<OutputPresenter::Image>> images;
-  images.reserve(num_images);
-
-  // Create an image for each buffer in the collection.
-  for (size_t i = 0; i < num_images; ++i) {
-    auto image = std::make_unique<PresenterImageFuchsia>(
-        shared_image_factory_, shared_image_representation_factory_,
-        dependency_);
-    if (!image->Initialize(frame_size_, color_space, si_format_, image_usage)) {
-      return {};
-    }
-    images.push_back(std::move(image));
-  }
-
-  return images;
+  NOTREACHED();
+  return {};
 }
 
 void OutputPresenterFuchsia::Present(
     SwapCompletionCallback completion_callback,
     BufferPresentedCallback presentation_callback,
     gfx::FrameData data) {
-  // SwapBuffer() should be called only after SchedulePrimaryPlane().
+  // SwapBuffer() should be called only after scheduling primary plane.
   DCHECK(next_frame_ && next_frame_->native_pixmap);
 
-  next_frame_->completion_callback = std::move(completion_callback);
-  next_frame_->presentation_callback = std::move(presentation_callback);
+  PendingFrame& frame = next_frame_.value();
+  window_surface_->Present(
+      std::move(frame.native_pixmap), std::move(frame.overlays),
+      std::move(frame.acquire_fences), std::move(frame.release_fences),
+      std::move(completion_callback), std::move(presentation_callback));
 
-  PresentNextFrame();
+  next_frame_.reset();
 }
 
 void OutputPresenterFuchsia::SchedulePrimaryPlane(
     const OverlayProcessorInterface::OutputSurfaceOverlayPlane& plane,
     Image* image,
     bool is_submitted) {
-  auto* image_fuchsia = static_cast<PresenterImageFuchsia*>(image);
-
-  if (!next_frame_)
-    next_frame_.emplace();
-  DCHECK(!next_frame_->native_pixmap);
-  next_frame_->native_pixmap = image_fuchsia->GetNativePixmap();
-
-  // Take semaphores for the image to be passed to ImagePipe::PresentImage().
-  image_fuchsia->TakePresentationFences(next_frame_->acquire_fences,
-                                        next_frame_->release_fences);
-  DCHECK(!next_frame_->acquire_fences.empty());
-  DCHECK(!next_frame_->release_fences.empty());
+  NOTREACHED();
 }
 
 void OutputPresenterFuchsia::ScheduleOverlayPlane(
@@ -257,7 +118,6 @@
 
   if (!next_frame_)
     next_frame_.emplace();
-  DCHECK(next_frame_->overlays.empty());
 
   DCHECK(overlay_plane_candidate.mailbox.IsSharedImage());
   auto pixmap = access ? access->GetNativePixmap() : nullptr;
@@ -268,7 +128,6 @@
   }
 
   if (overlay_plane_candidate.is_root_render_pass) {
-    DCHECK(features::ShouldRendererAllocateImages());
     DCHECK(!next_frame_->native_pixmap);
     next_frame_->native_pixmap = std::move(pixmap);
 
@@ -289,8 +148,10 @@
     // for the current access directly.
     access->SetReleaseFence(std::move(release_fence));
   } else {
-    next_frame_->overlays.emplace_back();
-    auto& overlay = next_frame_->overlays.back();
+    // Max one overlay plane supported.
+    DCHECK(next_frame_->overlays.empty());
+
+    auto& overlay = next_frame_->overlays.emplace_back();
     overlay.pixmap = std::move(pixmap);
     overlay.overlay_plane_data = gfx::OverlayPlaneData(
         overlay_plane_candidate.plane_z_order,
@@ -305,17 +166,4 @@
   }
 }
 
-void OutputPresenterFuchsia::PresentNextFrame() {
-  DCHECK(next_frame_);
-
-  PendingFrame& frame = next_frame_.value();
-  window_surface_->Present(
-      std::move(frame.native_pixmap), std::move(frame.overlays),
-      std::move(frame.acquire_fences), std::move(frame.release_fences),
-      std::move(frame.completion_callback),
-      std::move(frame.presentation_callback));
-
-  next_frame_.reset();
-}
-
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/output_presenter_fuchsia.h b/components/viz/service/display_embedder/output_presenter_fuchsia.h
index 1fb9490..bf46a53e 100644
--- a/components/viz/service/display_embedder/output_presenter_fuchsia.h
+++ b/components/viz/service/display_embedder/output_presenter_fuchsia.h
@@ -24,15 +24,10 @@
  public:
   static std::unique_ptr<OutputPresenterFuchsia> Create(
       ui::PlatformWindowSurface* window_surface,
-      SkiaOutputSurfaceDependency* deps,
-      gpu::SharedImageFactory* shared_image_factory,
-      gpu::SharedImageRepresentationFactory* representation_factory);
+      SkiaOutputSurfaceDependency* deps);
 
-  OutputPresenterFuchsia(
-      ui::PlatformWindowSurface* window_surface,
-      SkiaOutputSurfaceDependency* deps,
-      gpu::SharedImageFactory* shared_image_factory,
-      gpu::SharedImageRepresentationFactory* representation_factory);
+  OutputPresenterFuchsia(ui::PlatformWindowSurface* window_surface,
+                         SkiaOutputSurfaceDependency* deps);
   ~OutputPresenterFuchsia() override;
 
   // OutputPresenter implementation:
@@ -65,29 +60,18 @@
     PendingFrame(PendingFrame&&);
     PendingFrame& operator=(PendingFrame&&);
 
+    // Primary plane pixmap.
     scoped_refptr<gfx::NativePixmap> native_pixmap;
 
     std::vector<gfx::GpuFenceHandle> acquire_fences;
     std::vector<gfx::GpuFenceHandle> release_fences;
 
-    SwapCompletionCallback completion_callback;
-    BufferPresentedCallback presentation_callback;
-
     // Vector of overlays that are associated with this frame.
     std::vector<ui::OverlayPlane> overlays;
   };
 
-  void PresentNextFrame();
-
   ui::PlatformWindowSurface* const window_surface_;
   SkiaOutputSurfaceDependency* const dependency_;
-  gpu::SharedImageFactory* const shared_image_factory_;
-  gpu::SharedImageRepresentationFactory* const
-      shared_image_representation_factory_;
-
-  gfx::Size frame_size_;
-  SharedImageFormat si_format_ =
-      SharedImageFormat::SinglePlane(ResourceFormat::RGBA_8888);
 
   // The next frame to be submitted by SwapBuffers().
   absl::optional<PendingFrame> next_frame_;
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
index 4364254..a31e60b 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
@@ -394,7 +394,6 @@
 }
 
 SkiaOutputDeviceDCompPresenter::SkiaOutputDeviceDCompPresenter(
-    gpu::SharedImageFactory* shared_image_factory,
     gpu::SharedImageRepresentationFactory* shared_image_representation_factory,
     gpu::SharedContextState* context_state,
     scoped_refptr<gl::Presenter> presenter,
@@ -406,18 +405,16 @@
                             std::move(feature_info),
                             memory_tracker,
                             std::move(did_swap_buffer_complete_callback)),
-      presenter_(std::move(presenter)),
-      shared_image_factory_(shared_image_factory) {
+      presenter_(std::move(presenter)) {
   DCHECK(presenter_);
   DCHECK(presenter_->SupportsGpuVSync());
 
   capabilities_.supports_delegated_ink = presenter_->SupportsDelegatedInk();
   capabilities_.pending_swap_params.max_pending_swaps = 1;
+  capabilities_.renderer_allocates_images = true;
 }
 
-SkiaOutputDeviceDCompPresenter::~SkiaOutputDeviceDCompPresenter() {
-  DestroyRootSurface();
-}
+SkiaOutputDeviceDCompPresenter::~SkiaOutputDeviceDCompPresenter() = default;
 
 bool SkiaOutputDeviceDCompPresenter::Reshape(
     const SkSurfaceCharacterization& characterization,
@@ -431,28 +428,14 @@
     return false;
   }
 
-  if (characterization_ != characterization || color_space_ != color_space ||
-      device_scale_factor_ != device_scale_factor || transform_ != transform) {
-    characterization_ = characterization;
-    color_space_ = color_space;
-    device_scale_factor_ = device_scale_factor;
-    transform_ = transform;
-    DestroyRootSurface();
-  }
-
-  // The |SkiaOutputDeviceDCompPresenter| alpha state depends on
-  // |characterization_| and |wants_dcomp_surface_|. Since |presenter_| can only
-  // be |DCompPresenter| and its |Resize| function ignores the |has_alpha|
-  // parameter, we opt to pass an arbitrary value that we expect to be ignored.
-  constexpr bool kDCompPresenterResizeHasAlphaIgnore = false;
-
-  auto size = gfx::SkISizeToSize(characterization_.dimensions());
+  auto size = gfx::SkISizeToSize(characterization.dimensions());
 
   // DCompPresenter calls SetWindowPos on resize, so we call it to reflect the
   // newly allocated root surface.
   // Note, we could inline SetWindowPos here, but we need access to the HWND.
-  if (!presenter_->Resize(size, device_scale_factor_, color_space_,
-                          kDCompPresenterResizeHasAlphaIgnore)) {
+  if (!presenter_->Resize(
+          size, device_scale_factor, color_space,
+          !SkAlphaTypeIsOpaque(characterization.imageInfo().alphaType()))) {
     CheckForLoopFailures();
     // To prevent tail call, so we can see the stack.
     base::debug::Alias(nullptr);
@@ -466,160 +449,21 @@
 
 bool SkiaOutputDeviceDCompPresenter::SetDrawRectangle(
     const gfx::Rect& draw_rectangle) {
-  if (update_rect_.has_value()) {
-    DLOG(ERROR) << "SetDrawRectangle must be called only once per "
-                   "BeginPaint/EndPaint pair";
-    return false;
-  }
-
-  if (!presenter_->SetDrawRectangle(draw_rectangle)) {
-    return false;
-  }
-
-  update_rect_ = draw_rectangle;
-  return true;
-}
-
-void SkiaOutputDeviceDCompPresenter::SetEnableDCLayers(bool enable) {
-  if (want_dcomp_surface_ != enable) {
-    want_dcomp_surface_ = enable;
-
-    // Changing this value will require a new root SharedImage
-    DestroyRootSurface();
-  }
+  return presenter_->SetDrawRectangle(draw_rectangle);
 }
 
 void SkiaOutputDeviceDCompPresenter::SetGpuVSyncEnabled(bool enabled) {
   presenter_->SetGpuVSyncEnabled(enabled);
 }
 
-bool SkiaOutputDeviceDCompPresenter::EnsureRootSurfaceAllocated() {
-  DCHECK(characterization_.isValid()) << "Must call Reshape first";
-
-  if (root_surface_mailbox_.IsZero()) {
-    ResourceFormat resource_format =
-        SkColorTypeToResourceFormat(characterization_.colorType());
-
-    const gfx::Size size = gfx::SkISizeToSize(characterization_.dimensions());
-
-    // If |want_dcomp_surface_|, it means we are layering the root surface with
-    // videos that might be underlays. In this case, we want to force
-    // transparency so the underlay appears correctly.
-    SkAlphaType alpha_type = want_dcomp_surface_
-                                 ? kPremul_SkAlphaType
-                                 : characterization_.imageInfo().alphaType();
-
-    // TODO(tangm): DComp surfaces do not support RGB10A2 so we must fall back
-    // to swap chains. If this happens with video overlays, this can result in
-    // the video overlay and its parent surface having unsynchronized updates.
-    // We should clean this up by either avoiding HDR or using RGBAF32 surfaces
-    // in this case.
-    const bool dcomp_unsupported_format =
-        resource_format == ResourceFormat::RGBA_1010102;
-
-    uint32_t usage =
-        gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE | gpu::SHARED_IMAGE_USAGE_SCANOUT;
-    if (want_dcomp_surface_ && !dcomp_unsupported_format) {
-      usage |= gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE;
-    } else {
-      usage |= gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
-    }
-
-    gpu::Mailbox root_surface_mailbox = gpu::Mailbox::GenerateForSharedImage();
-    bool success = shared_image_factory_->CreateSharedImage(
-        root_surface_mailbox, SharedImageFormat::SinglePlane(resource_format),
-        size, color_space_, kTopLeft_GrSurfaceOrigin, alpha_type,
-        gpu::kNullSurfaceHandle, usage);
-    if (!success) {
-      CheckForLoopFailures();
-      // To prevent tail call, so we can see the stack.
-      base::debug::Alias(nullptr);
-      return false;
-    }
-
-    // Store the root surface's mailbox only on success.
-    root_surface_mailbox_ = root_surface_mailbox;
-  }
-
-  if (!root_surface_skia_representation_) {
-    DCHECK(!root_surface_mailbox_.IsZero());
-
-    root_surface_skia_representation_ =
-        shared_image_representation_factory_->ProduceSkia(
-            root_surface_mailbox_,
-            scoped_refptr<gpu::SharedContextState>(context_state_));
-
-    if (!root_surface_skia_representation_) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-void SkiaOutputDeviceDCompPresenter::DestroyRootSurface() {
-  root_surface_write_access_.reset();
-  root_surface_skia_representation_.reset();
-
-  if (!root_surface_mailbox_.IsZero()) {
-    shared_image_factory_->DestroySharedImage(root_surface_mailbox_);
-    root_surface_mailbox_.SetZero();
-  }
-}
-
 SkSurface* SkiaOutputDeviceDCompPresenter::BeginPaint(
     std::vector<GrBackendSemaphore>* end_semaphores) {
-  if (!EnsureRootSurfaceAllocated()) {
-    DLOG(ERROR) << "Could not create root SharedImage";
-    return nullptr;
-  }
-
-  DCHECK(root_surface_skia_representation_);
-  DCHECK(update_rect_.has_value());
-
-  std::vector<GrBackendSemaphore> begin_semaphores;
-  root_surface_write_access_ =
-      root_surface_skia_representation_->BeginScopedWriteAccess(
-          characterization_.sampleCount(), characterization_.surfaceProps(),
-          update_rect_.value(), &begin_semaphores, end_semaphores,
-          gpu::SkiaImageRepresentation::AllowUnclearedAccess::kYes, true);
-  update_rect_.reset();
-  if (!root_surface_write_access_) {
-    return nullptr;
-  }
-
-  // We don't expect any semaphores on a Windows, non-Vulkan backend.
-  DCHECK(begin_semaphores.empty());
-  DCHECK(end_semaphores->empty());
-
-  return root_surface_write_access_->surface();
-}
-
-void SkiaOutputDeviceDCompPresenter::Submit(bool sync_cpu,
-                                            base::OnceClosure callback) {
-  if (root_surface_write_access_) {
-    // On Windows, we expect `end_state` to be null, since DX11 doesn't use
-    // resource states/barriers.
-    auto end_state = root_surface_write_access_->TakeEndState();
-    DCHECK_EQ(nullptr, end_state);
-  }
-
-  SkiaOutputDevice::Submit(sync_cpu, std::move(callback));
+  NOTIMPLEMENTED();
+  return nullptr;
 }
 
 void SkiaOutputDeviceDCompPresenter::EndPaint() {
-  DCHECK(root_surface_skia_representation_);
-  DCHECK(root_surface_write_access_);
-
-  // Assume the caller has drawn to everything since the first update rect is
-  // required to cover the whole surface.
-  root_surface_skia_representation_->SetCleared();
-
-  root_surface_write_access_.reset();
-}
-
-bool SkiaOutputDeviceDCompPresenter::IsRootSurfaceAllocatedForTesting() const {
-  return !root_surface_mailbox_.IsZero();
+  NOTIMPLEMENTED();
 }
 
 bool SkiaOutputDeviceDCompPresenter::ScheduleDCLayer(
@@ -627,45 +471,19 @@
   return presenter_->ScheduleDCLayer(std::move(params));
 }
 
+bool SkiaOutputDeviceDCompPresenter::IsPrimaryPlaneOverlay() const {
+  return true;
+}
+
 void SkiaOutputDeviceDCompPresenter::DoPresent(
     const gfx::Rect& rect,
     gl::Presenter::SwapCompletionCallback completion_callback,
     BufferPresentedCallback feedback,
     gfx::FrameData data) {
-  if (!ScheduleRootSurfaceAsOverlay()) {
-    std::move(completion_callback)
-        .Run(gfx::SwapCompletionResult(gfx::SwapResult::SWAP_FAILED));
-    // Notify the caller, the buffer is never presented on a screen.
-    std::move(feedback).Run(gfx::PresentationFeedback::Failure());
-    return;
-  }
-
   // The |rect| is ignored because SetDrawRectangle specified the area to be
   // swapped.
   presenter_->Present(std::move(completion_callback), std::move(feedback),
                       data);
 }
 
-bool SkiaOutputDeviceDCompPresenter::ScheduleRootSurfaceAsOverlay() {
-  auto overlay = shared_image_representation_factory_->ProduceOverlay(
-      root_surface_mailbox_);
-  if (!overlay) {
-    return false;
-  }
-
-  auto read_access = overlay->BeginScopedReadAccess();
-  if (!read_access) {
-    return false;
-  }
-
-  auto params = std::make_unique<gl::DCLayerOverlayParams>();
-  params->z_order = 0;
-  params->quad_rect = gfx::Rect(overlay->size());
-  params->content_rect = params->quad_rect;
-  params->overlay_image = read_access->GetDCLayerOverlayImage();
-  ScheduleDCLayer(std::move(params));
-
-  return true;
-}
-
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.h b/components/viz/service/display_embedder/skia_output_device_dcomp.h
index fe770cf76..1642eed3 100644
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.h
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.h
@@ -27,7 +27,6 @@
 namespace gpu {
 class SharedContextState;
 class SharedImageRepresentationFactory;
-class SharedImageFactory;
 
 namespace gles2 {
 class FeatureInfo;
@@ -148,7 +147,6 @@
     : public SkiaOutputDeviceDComp {
  public:
   SkiaOutputDeviceDCompPresenter(
-      gpu::SharedImageFactory* shared_image_factory,
       gpu::SharedImageRepresentationFactory*
           shared_image_representation_factory,
       gpu::SharedContextState* context_state,
@@ -164,15 +162,12 @@
                const gfx::ColorSpace& color_space,
                float device_scale_factor,
                gfx::OverlayTransform transform) override;
-  void Submit(bool sync_cpu, base::OnceClosure callback) override;
   bool SetDrawRectangle(const gfx::Rect& draw_rectangle) override;
-  void SetEnableDCLayers(bool enable) override;
   void SetGpuVSyncEnabled(bool enabled) override;
   SkSurface* BeginPaint(
       std::vector<GrBackendSemaphore>* end_semaphores) override;
   void EndPaint() override;
-
-  bool IsRootSurfaceAllocatedForTesting() const;
+  bool IsPrimaryPlaneOverlay() const override;
 
  protected:
   bool ScheduleDCLayer(
@@ -183,39 +178,9 @@
                  gfx::FrameData data) override;
 
  private:
-  // Idempotent
-  bool EnsureRootSurfaceAllocated();
-  // Idempotent
-  void DestroyRootSurface();
-
-  // Returns true on success.
-  bool ScheduleRootSurfaceAsOverlay();
-
   // Any implementation capable of scheduling a DComp layer. Currently only
   // |DCompPresenter|.
   scoped_refptr<gl::Presenter> presenter_;
-
-  const raw_ptr<gpu::SharedImageFactory> shared_image_factory_;
-
-  // Parameters from the most recent |Reshape|.
-  SkSurfaceCharacterization characterization_;
-  gfx::ColorSpace color_space_;
-  float device_scale_factor_ = 1.0;
-  gfx::OverlayTransform transform_;
-
-  // Valid from SetDrawRectangle to BeginPaint
-  absl::optional<gfx::Rect> update_rect_;
-
-  bool want_dcomp_surface_ = false;
-
-  // Valid from |EnsureRootSurfaceAllocated| to |DestroyRootSurface|.
-  gpu::Mailbox root_surface_mailbox_;
-  // Valid from |EnsureRootSurfaceAllocated| to |DestroyRootSurface|.
-  std::unique_ptr<gpu::SkiaImageRepresentation>
-      root_surface_skia_representation_;
-  // Valid from BeginPaint to EndPaint
-  std::unique_ptr<gpu::SkiaImageRepresentation::ScopedWriteAccess>
-      root_surface_write_access_;
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc b/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc
deleted file mode 100644
index 636bfab..0000000
--- a/components/viz/service/display_embedder/skia_output_device_dcomp_unittest.cc
+++ /dev/null
@@ -1,373 +0,0 @@
-// 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/viz/service/display_embedder/skia_output_device_dcomp.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/memory/scoped_refptr.h"
-#include "components/viz/common/resources/shared_image_format.h"
-#include "gpu/command_buffer/common/context_creation_attribs.h"
-#include "gpu/command_buffer/common/mailbox.h"
-#include "gpu/command_buffer/common/shared_image_usage.h"
-#include "gpu/command_buffer/service/feature_info.h"
-#include "gpu/command_buffer/service/service_utils.h"
-#include "gpu/command_buffer/service/shared_context_state.h"
-#include "gpu/command_buffer/service/shared_image/shared_image_backing.h"
-#include "gpu/command_buffer/service/shared_image/shared_image_backing_factory.h"
-#include "gpu/command_buffer/service/shared_image/shared_image_factory.h"
-#include "gpu/command_buffer/service/shared_image/test_image_backing.h"
-#include "gpu/config/gpu_driver_bug_workarounds.h"
-#include "gpu/config/gpu_feature_info.h"
-#include "gpu/config/gpu_preferences.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/skia/include/core/SkAlphaType.h"
-#include "third_party/skia/include/core/SkColorType.h"
-#include "third_party/skia/include/gpu/GrTypes.h"
-#include "ui/gfx/color_space.h"
-#include "ui/gfx/geometry/size.h"
-#include "ui/gfx/surface_origin.h"
-#include "ui/gl/dcomp_presenter.h"
-#include "ui/gl/direct_composition_surface_win.h"
-#include "ui/gl/gl_context.h"
-#include "ui/gl/gl_implementation.h"
-#include "ui/gl/gl_share_group.h"
-#include "ui/gl/gl_surface.h"
-#include "ui/gl/init/gl_factory.h"
-#include "ui/gl/presenter.h"
-
-namespace viz {
-
-namespace {
-
-class TestSharedImageBackingFactory : public gpu::SharedImageBackingFactory {
- public:
-  TestSharedImageBackingFactory() : SharedImageBackingFactory(kUsageAll) {}
-
-  MOCK_METHOD(std::unique_ptr<gpu::SharedImageBacking>,
-              CreateSharedImage,
-              (const gpu::Mailbox& mailbox,
-               SharedImageFormat format,
-               gpu::SurfaceHandle surface_handle,
-               const gfx::Size& size,
-               const gfx::ColorSpace& color_space,
-               GrSurfaceOrigin surface_origin,
-               SkAlphaType alpha_type,
-               uint32_t usage,
-               bool is_thread_safe),
-              (override));
-
-  void SetCreateSharedImageSuccessByDefault() {
-    ON_CALL(*this, CreateSharedImage)
-        .WillByDefault(
-            [](const gpu::Mailbox& mailbox, SharedImageFormat format,
-               gpu::SurfaceHandle surface_handle, const gfx::Size& size,
-               const gfx::ColorSpace& color_space,
-               GrSurfaceOrigin surface_origin, SkAlphaType alpha_type,
-               uint32_t usage, bool is_thread_safe) {
-              return std::make_unique<gpu::TestImageBacking>(
-                  mailbox, format, size, color_space, surface_origin,
-                  alpha_type, usage, 1);
-            });
-  }
-
-  std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
-      const gpu::Mailbox& mailbox,
-      SharedImageFormat format,
-      const gfx::Size& size,
-      const gfx::ColorSpace& color_space,
-      GrSurfaceOrigin surface_origin,
-      SkAlphaType alpha_type,
-      uint32_t usage,
-      base::span<const uint8_t> pixel_data) override {
-    NOTREACHED();
-    return nullptr;
-  }
-  std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
-      const gpu::Mailbox& mailbox,
-      SharedImageFormat format,
-      const gfx::Size& size,
-      const gfx::ColorSpace& color_space,
-      GrSurfaceOrigin surface_origin,
-      SkAlphaType alpha_type,
-      uint32_t usage,
-      gfx::GpuMemoryBufferHandle handle) override {
-    NOTREACHED();
-    return nullptr;
-  }
-  std::unique_ptr<gpu::SharedImageBacking> CreateSharedImage(
-      const gpu::Mailbox& mailbox,
-      gfx::GpuMemoryBufferHandle handle,
-      gfx::BufferFormat format,
-      gfx::BufferPlane plane,
-      const gfx::Size& size,
-      const gfx::ColorSpace& color_space,
-      GrSurfaceOrigin surface_origin,
-      SkAlphaType alpha_type,
-      uint32_t usage) override {
-    NOTREACHED();
-    return nullptr;
-  }
-  bool IsSupported(uint32_t usage,
-                   SharedImageFormat format,
-                   const gfx::Size& size,
-                   bool thread_safe,
-                   gfx::GpuMemoryBufferType gmb_type,
-                   gpu::GrContextType gr_context_type,
-                   base::span<const uint8_t> pixel_data) override {
-    return true;
-  }
-};
-
-// No-op surface compatible with SkiaOutputDeviceDCompPresenter
-class NoopDCompPresenter : public gl::Presenter {
- public:
-  NoopDCompPresenter() = default;
-
-  bool SupportsGpuVSync() const override { return true; }
-  bool SupportsDelegatedInk() override { return false; }
-
-  bool SetDrawRectangle(const gfx::Rect& rectangle) override { return true; }
-
-  void Present(SwapCompletionCallback completion_callback,
-               PresentationCallback presentation_callback,
-               gfx::FrameData data) override {
-    NOTREACHED();
-  }
-
- protected:
-  ~NoopDCompPresenter() override = default;
-};
-
-}  // namespace
-
-class SkiaOutputDeviceDCompTest : public testing::Test {
- public:
-  SkiaOutputDeviceDCompTest() {}
-
- protected:
-  void SetUp() override {
-    gpu::GpuDriverBugWorkarounds workarounds;
-    gpu::GpuPreferences gpu_preferences;
-    gpu_preferences.use_passthrough_cmd_decoder = true;
-    gpu_preferences.enable_gpu_debugging = true;
-    gpu::GpuFeatureInfo gpu_feature_info;
-
-    scoped_refptr<gl::GLShareGroup> share_group = new gl::GLShareGroup();
-    scoped_refptr<gl::GLSurface> surface = gl::init::CreateOffscreenGLSurface(
-        gl::GLSurfaceEGL::GetGLDisplayEGL(), gfx::Size());
-
-    gpu::ContextCreationAttribs attribs_helper;
-    attribs_helper.context_type = gpu::CONTEXT_TYPE_OPENGLES3;
-    gl::GLContextAttribs attribs = gpu::gles2::GenerateGLContextAttribs(
-        attribs_helper, gpu_preferences.use_passthrough_cmd_decoder);
-    attribs.can_skip_validation = false;
-    scoped_refptr<gl::GLContext> context =
-        gl::init::CreateGLContext(share_group.get(), surface.get(), attribs);
-    ASSERT_NE(nullptr, context);
-    ASSERT_EQ(context->share_group(), share_group.get());
-    gpu_feature_info.ApplyToGLContext(context.get());
-    auto feature_info = base::MakeRefCounted<gpu::gles2::FeatureInfo>(
-        workarounds, gpu_feature_info);
-    ASSERT_TRUE(context->MakeCurrent(surface.get()));
-
-    context_state_ = base::MakeRefCounted<gpu::SharedContextState>(
-        std::move(share_group), surface, std::move(context),
-        false /* use_virtualized_gl_contexts */, base::DoNothing());
-    ASSERT_TRUE(context_state_->MakeCurrent(surface.get()));
-    ASSERT_TRUE(context_state_->InitializeGL(gpu_preferences, feature_info));
-    ASSERT_TRUE(context_state_->InitializeGrContext(
-        gpu_preferences, workarounds, /*cache=*/nullptr));
-    EXPECT_EQ(gl::GetGLImplementation(), gl::kGLImplementationEGLANGLE);
-
-    shared_image_factory_ = std::make_unique<gpu::SharedImageFactory>(
-        gpu_preferences, workarounds, gpu_feature_info, context_state_.get(),
-        &shared_image_manager_, nullptr,
-        /*is_for_display_compositor=*/false);
-
-    test_shared_image_factory_ =
-        std::make_unique<TestSharedImageBackingFactory>();
-    test_shared_image_factory_->SetCreateSharedImageSuccessByDefault();
-    shared_image_factory_->RegisterSharedImageBackingFactoryForTesting(
-        test_shared_image_factory_.get());
-
-    shared_image_representation_factory_ =
-        std::make_unique<gpu::SharedImageRepresentationFactory>(
-            &shared_image_manager_, nullptr);
-
-    surface_ = base::MakeRefCounted<NoopDCompPresenter>();
-
-    output_device_ = base::WrapUnique(new SkiaOutputDeviceDCompPresenter(
-        shared_image_factory_.get(), shared_image_representation_factory_.get(),
-        context_state_.get(), surface_, feature_info, nullptr,
-        base::DoNothing()));
-  }
-
-  void TearDown() override {
-    output_device_.reset();
-    surface_.reset();
-    context_state_.reset();
-    test_shared_image_factory_.reset();
-    shared_image_factory_.reset();
-    shared_image_representation_factory_.reset();
-  }
-
-  void Reshape(const gfx::Size size,
-               SkColorType color_type,
-               bool has_alpha,
-               const gfx::ColorSpace& color_space) {
-    sk_sp<GrContextThreadSafeProxy> gr_thread_safe_proxy =
-        context_state_->gr_context()->threadSafeProxy();
-
-    SkImageInfo image_info = SkImageInfo::Make(
-        SkISize::Make(size.width(), size.height()), color_type,
-        has_alpha ? kPremul_SkAlphaType : kOpaque_SkAlphaType);
-    GrBackendFormat backend_format = gr_thread_safe_proxy->defaultBackendFormat(
-        color_type, GrRenderable::kYes);
-
-    SkSurfaceCharacterization characterization =
-        gr_thread_safe_proxy->createCharacterization(
-            context_state_->gr_context()->getResourceCacheLimit(), image_info,
-            backend_format, 1, kTopLeft_GrSurfaceOrigin, SkSurfaceProps(),
-            false);
-    EXPECT_TRUE(
-        output_device_->Reshape(characterization, color_space, 1.0,
-                                gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE));
-
-    last_reshape_size_ = size;
-  }
-
-  // Do a fake draw to force the root surface to allocate.
-  void EnsureRootSurfaceAllocated() {
-    EXPECT_TRUE(
-        output_device_->SetDrawRectangle(gfx::Rect(last_reshape_size_)));
-    std::vector<GrBackendSemaphore> end_semaphores;
-    EXPECT_NE(nullptr, output_device_->BeginPaint(&end_semaphores));
-    EXPECT_EQ(0u, end_semaphores.size());
-    output_device_->EndPaint();
-  }
-
-  std::unique_ptr<SkiaOutputDeviceDCompPresenter> output_device_;
-
-  std::unique_ptr<TestSharedImageBackingFactory> test_shared_image_factory_;
-
- private:
-  // Store the last size passed to Reshape so that EnsureRootSurfaceAllocated
-  // knows what update_rect to pass.
-  gfx::Size last_reshape_size_;
-
-  scoped_refptr<gpu::SharedContextState> context_state_;
-  std::unique_ptr<gpu::SharedImageFactory> shared_image_factory_;
-  gpu::SharedImageManager shared_image_manager_;
-  std::unique_ptr<gpu::SharedImageRepresentationFactory>
-      shared_image_representation_factory_;
-  scoped_refptr<NoopDCompPresenter> surface_;
-};
-
-// Tests that switching using EnableDCLayers works.
-TEST_F(SkiaOutputDeviceDCompTest, DXGIDCLayerSwitch) {
-  Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true,
-          gfx::ColorSpace::CreateSRGB());
-
-  // Check we allocate a a DXGI swap chain when asked.
-  output_device_->SetEnableDCLayers(false);
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EXPECT_CALL(*test_shared_image_factory_,
-              CreateSharedImage(testing::_, testing::_, testing::_, testing::_,
-                                testing::_, testing::_, kPremul_SkAlphaType,
-                                gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
-                                    gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
-                                    gpu::SHARED_IMAGE_USAGE_SCANOUT,
-                                testing::_));
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  // Not changing the DC layer state should not affect anything. Note that since
-  // CreateSharedImage is mocked, EnsureRootSurfaceAllocated will cause the test
-  // to fail if it calls CreateSharedImage unexpectedly.
-  output_device_->SetEnableDCLayers(false);
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EnsureRootSurfaceAllocated();
-
-  // Check that switching DC layer state releases the root surface and allocated
-  // a DComp surface
-  output_device_->SetEnableDCLayers(true);
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EXPECT_CALL(*test_shared_image_factory_,
-              CreateSharedImage(
-                  testing::_, testing::_, testing::_, testing::_, testing::_,
-                  testing::_, SkAlphaType::kPremul_SkAlphaType,
-                  gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
-                      gpu::SHARED_IMAGE_USAGE_SCANOUT |
-                      gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE,
-                  testing::_));
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  // Not changing the DC layer state should not affect anything
-  output_device_->SetEnableDCLayers(true);
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EnsureRootSurfaceAllocated();
-
-  // Check that we can switch back to a swap chain
-  output_device_->SetEnableDCLayers(false);
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EXPECT_CALL(*test_shared_image_factory_,
-              CreateSharedImage(testing::_, testing::_, testing::_, testing::_,
-                                testing::_, testing::_,
-                                SkAlphaType::kPremul_SkAlphaType,
-                                gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
-                                    gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
-                                    gpu::SHARED_IMAGE_USAGE_SCANOUT,
-                                testing::_));
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-}
-
-// Check that Reshape only destroys the root surface when its parameters change.
-TEST_F(SkiaOutputDeviceDCompTest, Reshape) {
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true,
-          gfx::ColorSpace::CreateSRGB());
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  // No parameters changed, root surface should remain allocated
-  Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, true,
-          gfx::ColorSpace::CreateSRGB());
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-
-  // A change in parameters results in the root surface being deallocated
-  Reshape(gfx::Size(100, 100), kRGBA_8888_SkColorType, false,
-          gfx::ColorSpace::CreateSRGB());
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-}
-
-// Check that we fallback to a swap chain when we want a DComp surface with an
-// unsupported pixel format.
-TEST_F(SkiaOutputDeviceDCompTest, HDR10ColorSpaceForcesSwapChain) {
-  output_device_->SetEnableDCLayers(true);
-  Reshape(gfx::Size(100, 100), kRGBA_1010102_SkColorType, true,
-          gfx::ColorSpace::CreateHDR10());
-  EXPECT_CALL(*test_shared_image_factory_,
-              CreateSharedImage(testing::_, testing::_, testing::_, testing::_,
-                                testing::_, testing::_,
-                                SkAlphaType::kPremul_SkAlphaType,
-                                gpu::SHARED_IMAGE_USAGE_DISPLAY_READ |
-                                    gpu::SHARED_IMAGE_USAGE_DISPLAY_WRITE |
-                                    gpu::SHARED_IMAGE_USAGE_SCANOUT,
-                                testing::_));
-  EXPECT_FALSE(output_device_->IsRootSurfaceAllocatedForTesting());
-  EnsureRootSurfaceAllocated();
-  EXPECT_TRUE(output_device_->IsRootSurfaceAllocatedForTesting());
-}
-
-}  // namespace viz
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index a184acc6..01ff2ccd 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -350,10 +350,9 @@
 
   auto sk_color_space =
       params.color_space.ToSkColorSpace(params.sdr_white_level);
-  characterization_ = CreateSkSurfaceCharacterization(
+  characterization_ = CreateSkSurfaceCharacterizationCurrentFrame(
       params.size, color_type, params.alpha_type, /*mipmap=*/false,
-      std::move(sk_color_space), /*is_root_render_pass=*/true,
-      /*is_overlay=*/false);
+      std::move(sk_color_space));
 
   // impl_on_gpu_ is released on the GPU thread by a posted task from
   // SkiaOutputSurfaceImpl::dtor. So it is safe to use base::Unretained.
@@ -657,6 +656,7 @@
     const gfx::Size& surface_size,
     ResourceFormat format,
     bool mipmap,
+    bool scanout_dcomp_surface,
     sk_sp<SkColorSpace> color_space,
     bool is_overlay,
     const gpu::Mailbox& mailbox) {
@@ -667,10 +667,10 @@
 
   SkColorType color_type =
       ResourceFormatToClosestSkColorType(/*gpu_compositing=*/true, format);
-  SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
-      surface_size, color_type, kPremul_SkAlphaType, mipmap,
-      std::move(color_space),
-      /*is_root_render_pass=*/false, is_overlay);
+  SkSurfaceCharacterization characterization =
+      CreateSkSurfaceCharacterizationRenderPass(
+          surface_size, color_type, kPremul_SkAlphaType, mipmap,
+          std::move(color_space), is_overlay, scanout_dcomp_surface);
   if (!characterization.isValid())
     return nullptr;
 
@@ -702,12 +702,12 @@
   // 8-bit unorm alpha channel to work. RGBA8 is always supported, so we use it.
   SkColorType color_type_with_alpha = SkColorType::kRGBA_8888_SkColorType;
 
-  SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
-      gfx::Size(characterization_.width(), characterization_.height()),
-      color_type_with_alpha, characterization_.imageInfo().alphaType(),
-      /*mipmap=*/false, characterization_.refColorSpace(),
-      /*is_root_render_pass=*/false,
-      /*is_overlay=*/false);
+  SkSurfaceCharacterization characterization =
+      CreateSkSurfaceCharacterizationRenderPass(
+          gfx::Size(characterization_.width(), characterization_.height()),
+          color_type_with_alpha, characterization_.imageInfo().alphaType(),
+          /*mipmap=*/false, characterization_.refColorSpace(),
+          /*is_overlay=*/false, /*scanout_dcomp_surface=*/false);
   if (characterization.isValid()) {
     overdraw_surface_recorder_.emplace(characterization);
     overdraw_canvas_.emplace((overdraw_surface_recorder_->getCanvas()));
@@ -975,14 +975,14 @@
 }
 
 SkSurfaceCharacterization
-SkiaOutputSurfaceImpl::CreateSkSurfaceCharacterization(
+SkiaOutputSurfaceImpl::CreateSkSurfaceCharacterizationRenderPass(
     const gfx::Size& surface_size,
     SkColorType color_type,
     SkAlphaType alpha_type,
     bool mipmap,
     sk_sp<SkColorSpace> color_space,
-    bool is_root_render_pass,
-    bool is_overlay) {
+    bool is_overlay,
+    bool scanout_dcomp_surface) const {
   if (!gr_context_thread_safe_) {
     DLOG(ERROR) << "gr_context_thread_safe_ is null.";
     return SkSurfaceCharacterization();
@@ -990,69 +990,6 @@
 
   auto cache_max_resource_bytes = impl_on_gpu_->max_resource_cache_bytes();
   SkSurfaceProps surface_props{0, kUnknown_SkPixelGeometry};
-  if (is_root_render_pass) {
-    DCHECK(!is_overlay);
-    int sample_count = std::min(
-        sample_count_,
-        gr_context_thread_safe_->maxSurfaceSampleCountForColorType(color_type));
-    auto backend_format = gr_context_thread_safe_->defaultBackendFormat(
-        color_type, GrRenderable::kYes);
-#if BUILDFLAG(IS_MAC)
-    DCHECK_EQ(dependency_->gr_context_type(), gpu::GrContextType::kGL);
-    // For root rander pass, IOSurface will be used, and we may need using
-    // GL_TEXTURE_RECTANGLE_ARB as texture target.
-    backend_format =
-        GrBackendFormat::MakeGL(backend_format.asGLFormatEnum(),
-                                gpu::GetPlatformSpecificTextureTarget());
-#endif
-    DCHECK(backend_format.isValid())
-        << "GrBackendFormat is invalid for color_type: " << color_type;
-    auto surface_origin =
-        capabilities_.output_surface_origin == gfx::SurfaceOrigin::kBottomLeft
-            ? kBottomLeft_GrSurfaceOrigin
-            : kTopLeft_GrSurfaceOrigin;
-    auto image_info =
-        SkImageInfo::Make(surface_size.width(), surface_size.height(),
-                          color_type, alpha_type, std::move(color_space));
-    DCHECK((capabilities_.uses_default_gl_framebuffer &&
-            dependency_->gr_context_type() == gpu::GrContextType::kGL) ||
-           !capabilities_.uses_default_gl_framebuffer);
-    // Skia doesn't support set desired MSAA count for default gl framebuffer.
-    if (capabilities_.uses_default_gl_framebuffer)
-      sample_count = 1;
-    bool is_textureable =
-        !capabilities_.uses_default_gl_framebuffer &&
-        !capabilities_.root_is_vulkan_secondary_command_buffer;
-    auto characterization = gr_context_thread_safe_->createCharacterization(
-        cache_max_resource_bytes, image_info, backend_format, sample_count,
-        surface_origin, surface_props, mipmap,
-        capabilities_.uses_default_gl_framebuffer, is_textureable,
-        GrProtected::kNo, /*vkRTSupportsInputAttachment=*/false,
-        capabilities_.root_is_vulkan_secondary_command_buffer);
-#if BUILDFLAG(ENABLE_VULKAN)
-    VkFormat vk_format = VK_FORMAT_UNDEFINED;
-#endif
-    LOG_IF(DFATAL, !characterization.isValid())
-        << "\n  surface_size=" << surface_size.ToString()
-        << "\n  format=" << static_cast<int>(color_type)
-        << "\n  color_type=" << static_cast<int>(color_type)
-        << "\n  backend_format.isValid()=" << backend_format.isValid()
-        << "\n  backend_format.backend()="
-        << static_cast<int>(backend_format.backend())
-        << "\n  backend_format.asGLFormat()="
-        << static_cast<int>(backend_format.asGLFormat())
-#if BUILDFLAG(ENABLE_VULKAN)
-        << "\n  backend_format.asVkFormat()="
-        << static_cast<int>(backend_format.asVkFormat(&vk_format))
-        << "\n  backend_format.asVkFormat() vk_format="
-        << static_cast<int>(vk_format)
-#endif
-        << "\n  sample_count=" << sample_count
-        << "\n  surface_origin=" << static_cast<int>(surface_origin)
-        << "\n  willGlFBO0=" << capabilities_.uses_default_gl_framebuffer;
-    return characterization;
-  }
-
   const int sample_count = std::min(
       sample_count_,
       gr_context_thread_safe_->maxSurfaceSampleCountForColorType(color_type));
@@ -1073,14 +1010,95 @@
       SkImageInfo::Make(surface_size.width(), surface_size.height(), color_type,
                         alpha_type, std::move(color_space));
 
+  // Skia draws to DComp surfaces by binding them to GLFB0, since the surfaces
+  // are write-only and cannot be wrapped as a GL texture.
+  if (scanout_dcomp_surface) {
+    DCHECK_EQ(backend_format.backend(), GrBackendApi::kOpenGL);
+  }
+
   auto characterization = gr_context_thread_safe_->createCharacterization(
       cache_max_resource_bytes, image_info, backend_format, sample_count,
       kTopLeft_GrSurfaceOrigin, surface_props, mipmap,
-      /*willUseGLFBO0=*/false, /*isTextureable=*/true, GrProtected::kNo);
+      /*willUseGLFBO0=*/scanout_dcomp_surface,
+      /*isTextureable=*/!scanout_dcomp_surface, GrProtected::kNo);
   DCHECK(characterization.isValid());
   return characterization;
 }
 
+SkSurfaceCharacterization
+SkiaOutputSurfaceImpl::CreateSkSurfaceCharacterizationCurrentFrame(
+    const gfx::Size& surface_size,
+    SkColorType color_type,
+    SkAlphaType alpha_type,
+    bool mipmap,
+    sk_sp<SkColorSpace> color_space) const {
+  if (!gr_context_thread_safe_) {
+    DLOG(ERROR) << "gr_context_thread_safe_ is null.";
+    return SkSurfaceCharacterization();
+  }
+
+  auto cache_max_resource_bytes = impl_on_gpu_->max_resource_cache_bytes();
+  SkSurfaceProps surface_props{0, kUnknown_SkPixelGeometry};
+  int sample_count = std::min(
+      sample_count_,
+      gr_context_thread_safe_->maxSurfaceSampleCountForColorType(color_type));
+  auto backend_format = gr_context_thread_safe_->defaultBackendFormat(
+      color_type, GrRenderable::kYes);
+#if BUILDFLAG(IS_MAC)
+  DCHECK_EQ(dependency_->gr_context_type(), gpu::GrContextType::kGL);
+  // For root rander pass, IOSurface will be used, and we may need using
+  // GL_TEXTURE_RECTANGLE_ARB as texture target.
+  backend_format = GrBackendFormat::MakeGL(
+      backend_format.asGLFormatEnum(), gpu::GetPlatformSpecificTextureTarget());
+#endif
+  DCHECK(backend_format.isValid())
+      << "GrBackendFormat is invalid for color_type: " << color_type;
+  auto surface_origin =
+      capabilities_.output_surface_origin == gfx::SurfaceOrigin::kBottomLeft
+          ? kBottomLeft_GrSurfaceOrigin
+          : kTopLeft_GrSurfaceOrigin;
+  auto image_info =
+      SkImageInfo::Make(surface_size.width(), surface_size.height(), color_type,
+                        alpha_type, std::move(color_space));
+  DCHECK((capabilities_.uses_default_gl_framebuffer &&
+          dependency_->gr_context_type() == gpu::GrContextType::kGL) ||
+         !capabilities_.uses_default_gl_framebuffer);
+  // Skia doesn't support set desired MSAA count for default gl framebuffer.
+  if (capabilities_.uses_default_gl_framebuffer) {
+    sample_count = 1;
+  }
+  bool is_textureable = !capabilities_.uses_default_gl_framebuffer &&
+                        !capabilities_.root_is_vulkan_secondary_command_buffer;
+  auto characterization = gr_context_thread_safe_->createCharacterization(
+      cache_max_resource_bytes, image_info, backend_format, sample_count,
+      surface_origin, surface_props, mipmap,
+      capabilities_.uses_default_gl_framebuffer, is_textureable,
+      GrProtected::kNo, /*vkRTSupportsInputAttachment=*/false,
+      capabilities_.root_is_vulkan_secondary_command_buffer);
+#if BUILDFLAG(ENABLE_VULKAN)
+  VkFormat vk_format = VK_FORMAT_UNDEFINED;
+#endif
+  LOG_IF(DFATAL, !characterization.isValid())
+      << "\n  surface_size=" << surface_size.ToString()
+      << "\n  format=" << static_cast<int>(color_type)
+      << "\n  color_type=" << static_cast<int>(color_type)
+      << "\n  backend_format.isValid()=" << backend_format.isValid()
+      << "\n  backend_format.backend()="
+      << static_cast<int>(backend_format.backend())
+      << "\n  backend_format.asGLFormat()="
+      << static_cast<int>(backend_format.asGLFormat())
+#if BUILDFLAG(ENABLE_VULKAN)
+      << "\n  backend_format.asVkFormat()="
+      << static_cast<int>(backend_format.asVkFormat(&vk_format))
+      << "\n  backend_format.asVkFormat() vk_format="
+      << static_cast<int>(vk_format)
+#endif
+      << "\n  sample_count=" << sample_count
+      << "\n  surface_origin=" << static_cast<int>(surface_origin)
+      << "\n  willGlFBO0=" << capabilities_.uses_default_gl_framebuffer;
+  return characterization;
+}
+
 void SkiaOutputSurfaceImpl::DidSwapBuffersComplete(
     gpu::SwapBuffersCompleteParams params,
     const gfx::Size& pixel_size,
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index e50fd72..35f6e25 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -116,6 +116,7 @@
                                  const gfx::Size& surface_size,
                                  ResourceFormat format,
                                  bool mipmap,
+                                 bool scanout_dcomp_surface,
                                  sk_sp<SkColorSpace> color_space,
                                  bool is_overlay,
                                  const gpu::Mailbox& mailbox) override;
@@ -188,14 +189,20 @@
   bool Initialize();
   void InitializeOnGpuThread(GpuVSyncCallback vsync_callback_runner,
                              bool* result);
-  SkSurfaceCharacterization CreateSkSurfaceCharacterization(
+  SkSurfaceCharacterization CreateSkSurfaceCharacterizationRenderPass(
       const gfx::Size& surface_size,
       SkColorType color_type,
       SkAlphaType alpha_type,
       bool mipmap,
       sk_sp<SkColorSpace> color_space,
-      bool is_root_render_pass,
-      bool is_overlay);
+      bool is_overlay,
+      bool scanout_dcomp_surface) const;
+  SkSurfaceCharacterization CreateSkSurfaceCharacterizationCurrentFrame(
+      const gfx::Size& surface_size,
+      SkColorType color_type,
+      SkAlphaType alpha_type,
+      bool mipmap,
+      sk_sp<SkColorSpace> color_space) const;
   void DidSwapBuffersComplete(gpu::SwapBuffersCompleteParams params,
                               const gfx::Size& pixel_size,
                               gfx::GpuFenceHandle release_fence);
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 7d2ddb9..134611e 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -665,7 +665,12 @@
   // create a raw pointer to it first for use within this function.
   gpu::SkiaImageRepresentation::ScopedWriteAccess* scoped_access =
       local_scoped_access.get();
-  if (is_overlay) {
+  // DComp only allows drawing to a single surface at a time and does not
+  // require us to keep the write accesses open through submit.
+  const bool is_dcomp_surface =
+      (local_scoped_access->representation()->usage() &
+       gpu::SHARED_IMAGE_USAGE_SCANOUT_DCOMP_SURFACE) != 0;
+  if (is_overlay && !is_dcomp_surface) {
     DCHECK(!overlay_pass_accesses_.contains(mailbox));
     overlay_pass_accesses_.emplace(mailbox, std::move(local_scoped_access));
   }
@@ -849,7 +854,7 @@
       std::vector<GrBackendSemaphore> end_semaphores;
 
       auto scoped_write = representation->BeginScopedWriteAccess(
-          /*final_msaa_count=*/1, surface_props, &begin_semaphores,
+          /*final_msaa_count=*/1, surface_props, gfx::Rect(), &begin_semaphores,
           &end_semaphores,
           gpu::SharedImageRepresentation::AllowUnclearedAccess::kYes);
 
@@ -1444,7 +1449,7 @@
       SkSurfaceProps surface_props{0, kUnknown_SkPixelGeometry};
       // TODO(https://crbug.com/1226672): Use BeginScopedReadAccess instead
       scoped_access = backing_representation->BeginScopedWriteAccess(
-          /*final_msaa_count=*/1, surface_props, &begin_semaphores,
+          /*final_msaa_count=*/1, surface_props, gfx::Rect(), &begin_semaphores,
           &end_semaphores,
           gpu::SharedImageRepresentation::AllowUnclearedAccess::kNo);
       surface = scoped_access->surface();
@@ -1759,7 +1764,6 @@
             GetDidSwapBuffersCompleteCallback());
 #else   // !BUILDFLAG(IS_WIN)
         output_device_ = std::make_unique<SkiaOutputDeviceDCompPresenter>(
-            shared_image_factory_.get(),
             shared_image_representation_factory_.get(), context_state_.get(),
             presenter_, feature_info_, shared_gpu_deps_->memory_tracker(),
             GetDidSwapBuffersCompleteCallback());
@@ -1839,9 +1843,8 @@
 #if !BUILDFLAG(IS_WIN)
   std::unique_ptr<OutputPresenter> output_presenter;
 #if BUILDFLAG(IS_FUCHSIA)
-  output_presenter = OutputPresenterFuchsia::Create(
-      window_surface_.get(), dependency_, shared_image_factory_.get(),
-      shared_image_representation_factory_.get());
+  output_presenter =
+      OutputPresenterFuchsia::Create(window_surface_.get(), dependency_);
 #else
   presenter_ = dependency_->CreatePresenter(weak_ptr_factory_.GetWeakPtr(),
                                             gl::GLSurfaceFormat());
@@ -2355,7 +2358,8 @@
   SharedImageFormat si_format = SharedImageFormat::SinglePlane(format);
   shared_image_factory_->CreateSharedImage(
       mailbox, si_format, size, color_space, kTopLeft_GrSurfaceOrigin,
-      kPremul_SkAlphaType, surface_handle, usage);
+      si_format.HasAlpha() ? kPremul_SkAlphaType : kOpaque_SkAlphaType,
+      surface_handle, usage);
   skia_representations_.emplace(mailbox, nullptr);
 }
 
diff --git a/components/viz/test/fake_skia_output_surface.cc b/components/viz/test/fake_skia_output_surface.cc
index 88101e8..529691f 100644
--- a/components/viz/test/fake_skia_output_surface.cc
+++ b/components/viz/test/fake_skia_output_surface.cc
@@ -170,6 +170,7 @@
     const gfx::Size& surface_size,
     ResourceFormat format,
     bool mipmap,
+    bool scanout_dcomp_surface,
     sk_sp<SkColorSpace> color_space,
     bool is_overlay,
     const gpu::Mailbox& mailbox) {
diff --git a/components/viz/test/fake_skia_output_surface.h b/components/viz/test/fake_skia_output_surface.h
index 3e05754..80786e5 100644
--- a/components/viz/test/fake_skia_output_surface.h
+++ b/components/viz/test/fake_skia_output_surface.h
@@ -69,6 +69,7 @@
                                  const gfx::Size& surface_size,
                                  ResourceFormat format,
                                  bool mipmap,
+                                 bool scanout_dcomp_surface,
                                  sk_sp<SkColorSpace> color_space,
                                  bool is_overlay,
                                  const gpu::Mailbox& mailbox) override;
diff --git a/content/app/android/library_loader_hooks.cc b/content/app/android/library_loader_hooks.cc
index 46be2515..2ee3c19 100644
--- a/content/app/android/library_loader_hooks.cc
+++ b/content/app/android/library_loader_hooks.cc
@@ -5,7 +5,6 @@
 #include "content/app/android/library_loader_hooks.h"
 
 #include "base/android/reached_code_profiler.h"
-#include "base/android/task_scheduler/task_runner_android.h"
 #include "base/logging.h"
 #include "base/process/current_process.h"
 #include "base/trace_event/trace_event.h"
@@ -56,11 +55,6 @@
             << ", default verbosity = " << logging::GetVlogVerbosity();
   }
 
-  // In Android Java, UI thread is a base/ concept, but needs to know how that
-  // maps onto the BrowserThread::UI in C++.
-  base::TaskRunnerAndroid::SetUiThreadExtension(
-      {BrowserTaskTraitsExtension::kExtensionId, {}});
-
   // Content Schemes need to be registered as early as possible after the
   // CommandLine has been initialized to allow java and tests to use GURL before
   // running ContentMain.
diff --git a/content/browser/media/media_internals.cc b/content/browser/media/media_internals.cc
index d8d83aa..ccf627a 100644
--- a/content/browser/media/media_internals.cc
+++ b/content/browser/media/media_internals.cc
@@ -499,11 +499,7 @@
   std::string chrome_wide_echo_cancellation_value_string =
       media::IsChromeWideEchoCancellationEnabled()
           ? base::StrCat(
-                {"Enabled, processing_fifo_size = ",
-                 base::NumberToString(
-                     media::kChromeWideEchoCancellationProcessingFifoSize
-                         .Get()),
-                 ", minimize_resampling = ",
+                {"Enabled, minimize_resampling = ",
                  media::kChromeWideEchoCancellationMinimizeResampling.Get()
                      ? "true"
                      : "false",
@@ -514,6 +510,16 @@
           : "Disabled";
   audio_info_data.Set(media::kChromeWideEchoCancellation.name,
                       base::Value(chrome_wide_echo_cancellation_value_string));
+
+  std::string decrease_processing_audio_fifo_size_value_string =
+      base::FeatureList::IsEnabled(media::kDecreaseProcessingAudioFifoSize)
+          ? base::StrCat(
+                {"Enabled, fifo_size = ",
+                 base::NumberToString(media::GetProcessingAudioFifoSize())})
+          : "Disabled";
+  audio_info_data.Set(
+      media::kDecreaseProcessingAudioFifoSize.name,
+      base::Value(decrease_processing_audio_fifo_size_value_string));
 #endif
   std::u16string audio_info_update =
       SerializeUpdate("media.updateGeneralAudioInformation", audio_info_data);
diff --git a/content/browser/memory_pressure/user_level_memory_pressure_signal_generator.cc b/content/browser/memory_pressure/user_level_memory_pressure_signal_generator.cc
index d7718e74..472f741 100644
--- a/content/browser/memory_pressure/user_level_memory_pressure_signal_generator.cc
+++ b/content/browser/memory_pressure/user_level_memory_pressure_signal_generator.cc
@@ -36,12 +36,6 @@
 
 namespace {
 constexpr uint64_t k1MB = 1024ull * 1024;
-}
-
-#if !defined(ARCH_CPU_64_BITS)
-
-namespace {
-
 constexpr base::TimeDelta kDefaultMeasurementInterval = base::Seconds(1);
 
 // Time interval between measuring total private memory footprint.
@@ -76,11 +70,9 @@
 }
 
 }  // namespace
-#endif  // !defined(ARCH_CPU_64_BITS)
 
 // static
 void UserLevelMemoryPressureSignalGenerator::Initialize() {
-#if !defined(ARCH_CPU_64_BITS)
   uint64_t physical_memory = base::SysInfo::AmountOfPhysicalMemory();
   constexpr uint64_t k1GB = 1024ull * k1MB;
 
@@ -117,7 +109,6 @@
   }
 
   // No group defined for >6 GB devices.
-#endif  // !defined(ARCH_CPU_64_BITS)
 }
 
 // static
diff --git a/content/browser/preloading/prerender/prerender_browsertest.cc b/content/browser/preloading/prerender/prerender_browsertest.cc
index 3ff4e991..1760cb0 100644
--- a/content/browser/preloading/prerender/prerender_browsertest.cc
+++ b/content/browser/preloading/prerender/prerender_browsertest.cc
@@ -9511,6 +9511,14 @@
       response->AddCustomHeader("Accept-CH", "sec-ch-ua-full-version");
     } else if (request.relative_url.find("bitness") != std::string::npos) {
       response->AddCustomHeader("Accept-CH", "sec-ch-ua-bitness");
+    } else if (request.relative_url.find("viewport-width") !=
+               std::string::npos) {
+      response->AddCustomHeader("Accept-CH", "viewport-width");
+      response->AddCustomHeader("Accept-CH", "sec-ch-viewport-width");
+    } else if (request.relative_url.find("viewport-height") !=
+               std::string::npos) {
+      // Don't need to add "viewport-height" as it is not defined in the specs.
+      response->AddCustomHeader("Accept-CH", "sec-ch-viewport-height");
     } else if (request.relative_url.find("no-value") != std::string::npos) {
       response->AddCustomHeader("Accept-CH", "");
     }
@@ -9687,6 +9695,74 @@
   EXPECT_TRUE(HasRequestHeader(real_navigate_url, "sec-ch-ua-full-version"));
 }
 
+// Test that changes on the viewport width of the initiator page between when to
+// trigger prerendering and when to activate don't fail activation params match.
+IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest, ViewPort_Width) {
+  MockClientHintsControllerDelegate client_hints_controller_delegate(
+      GetShellUserAgentMetadata());
+  ShellContentBrowserClient::Get()
+      ->browser_context()
+      ->set_client_hints_controller_delegate(&client_hints_controller_delegate);
+
+  // Set the initial window size.
+  web_contents_impl()->Resize(gfx::Rect(10, 20));
+
+  // Navigate to an initial page.
+  GURL url = GetUrl("/empty.html?acceptch-viewport-width");
+  ASSERT_TRUE(NavigateToURL(shell(), url));
+
+  // Start prerendering. This should have the "(sec-ch-)viewport-width" headers.
+  GURL prerender_url = GetUrl("/iframe.html?acceptch");
+  int host_id = AddPrerender(prerender_url);
+  WaitForPrerenderLoadCompleted(host_id);
+  EXPECT_TRUE(HasRequestHeader(prerender_url, "viewport-width"));
+  EXPECT_TRUE(HasRequestHeader(prerender_url, "sec-ch-viewport-width"));
+
+  // Resize the window.
+  web_contents_impl()->Resize(gfx::Rect(30, 40));
+
+  // Activation should also have the "(sec-ch-)viewport-width" headers but their
+  // value is different from prerender initial navigation. This shouldn't fail
+  // prerender activation parameter match.
+  test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
+  NavigatePrimaryPage(prerender_url);
+  prerender_observer.WaitForActivation();
+}
+
+// Test that changes on the viewport height of the initiator page between when
+// to trigger prerendering and when to activate don't fail activation params
+// match.
+IN_PROC_BROWSER_TEST_F(PrerenderClientHintsBrowserTest, ViewPort_Height) {
+  MockClientHintsControllerDelegate client_hints_controller_delegate(
+      GetShellUserAgentMetadata());
+  ShellContentBrowserClient::Get()
+      ->browser_context()
+      ->set_client_hints_controller_delegate(&client_hints_controller_delegate);
+
+  // Set the initial window size.
+  web_contents_impl()->Resize(gfx::Rect(10, 20));
+
+  // Navigate to an initial page.
+  GURL url = GetUrl("/empty.html?acceptch-viewport-height");
+  ASSERT_TRUE(NavigateToURL(shell(), url));
+
+  // Start prerendering. This should have the "sec-ch-viewport-height" header.
+  GURL prerender_url = GetUrl("/iframe.html?acceptch");
+  int host_id = AddPrerender(prerender_url);
+  WaitForPrerenderLoadCompleted(host_id);
+  EXPECT_TRUE(HasRequestHeader(prerender_url, "sec-ch-viewport-height"));
+
+  // Resize the window.
+  web_contents_impl()->Resize(gfx::Rect(30, 40));
+
+  // Activation should also have the "sec-ch-viewport-height" header but its
+  // value is different from prerender initial navigation. This shouldn't fail
+  // prerender activation parameter match.
+  test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id);
+  NavigatePrimaryPage(prerender_url);
+  prerender_observer.WaitForActivation();
+}
+
 void CheckExpectedCrossOriginMetrics(
     const base::HistogramTester& histogram_tester,
     PrerenderCrossOriginRedirectionMismatch mismatch_type,
diff --git a/content/browser/preloading/prerender/prerender_host.cc b/content/browser/preloading/prerender/prerender_host.cc
index 88e5d12..44c89860 100644
--- a/content/browser/preloading/prerender/prerender_host.cc
+++ b/content/browser/preloading/prerender/prerender_host.cc
@@ -69,6 +69,16 @@
   }
 #endif  // BUILDFLAG(IS_ANDROID)
 
+  // Remove the viewport headers as the viewport size of the initiator page can
+  // be changed during prerendering. See also https://crbug.com/1401244.
+  prerender_headers.RemoveHeader("viewport-width");
+  potential_activation_headers.RemoveHeader("viewport-width");
+  prerender_headers.RemoveHeader("sec-ch-viewport-width");
+  potential_activation_headers.RemoveHeader("sec-ch-viewport-width");
+  // Don't need to handle "viewport-height" as it is not defined in the specs.
+  prerender_headers.RemoveHeader("sec-ch-viewport-height");
+  potential_activation_headers.RemoveHeader("sec-ch-viewport-height");
+
   // Compare headers in serialized strings. The spec doesn't require serialized
   // string matches, but practically Chrome generates headers in a decisive way,
   // i.e. in the same order and cases. So we can expect serialized string
diff --git a/content/browser/scheduler/browser_task_executor.cc b/content/browser/scheduler/browser_task_executor.cc
index 02f6b9b..33ec361 100644
--- a/content/browser/scheduler/browser_task_executor.cc
+++ b/content/browser/scheduler/browser_task_executor.cc
@@ -24,6 +24,7 @@
 
 #if BUILDFLAG(IS_ANDROID)
 #include "base/android/task_scheduler/post_task_android.h"
+#include "base/android/task_scheduler/task_runner_android.h"
 #endif
 
 using QueueType = content::BrowserTaskQueues::QueueType;
@@ -223,6 +224,10 @@
       ->EnableAllExceptBestEffortQueues();
 
 #if BUILDFLAG(IS_ANDROID)
+  // In Android Java, UI thread is a base/ concept, but needs to know how that
+  // maps onto the BrowserThread::UI in C++.
+  base::TaskRunnerAndroid::SetUiThreadExtension(
+      {BrowserTaskTraitsExtension::kExtensionId, {}});
   base::PostTaskAndroid::SignalNativeSchedulerReady();
 #endif
 }
diff --git a/content/browser/web_contents/web_contents_observer_browsertest.cc b/content/browser/web_contents/web_contents_observer_browsertest.cc
index 258c850e..86ed03b8 100644
--- a/content/browser/web_contents/web_contents_observer_browsertest.cc
+++ b/content/browser/web_contents/web_contents_observer_browsertest.cc
@@ -553,7 +553,8 @@
 }
 
 // TODO(https://crbug.com/1288573): Flaky on Mac.
-#if BUILDFLAG(IS_MAC)
+// TODO(https://crbug.com/1426973): Fix on android and enable it.
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
 #define MAYBE_CookieCallbacks_Subresource DISABLED_CookieCallbacks_Subresource
 #else
 #define MAYBE_CookieCallbacks_Subresource CookieCallbacks_Subresource
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index 3921f72..4e6a543 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -908,7 +908,6 @@
   }
 
   v8::Local<v8::Value> trusted_signals;
-  absl::optional<uint32_t> bidding_signals_data_version;
   if (!trusted_bidding_signals_result ||
       !bidder_worklet_non_shared_params.trusted_bidding_signals_keys ||
       bidder_worklet_non_shared_params.trusted_bidding_signals_keys->empty()) {
@@ -917,10 +916,14 @@
     trusted_signals = trusted_bidding_signals_result->GetBiddingSignals(
         v8_helper_.get(), context,
         *bidder_worklet_non_shared_params.trusted_bidding_signals_keys);
+  }
+  args.push_back(trusted_signals);
+
+  absl::optional<uint32_t> bidding_signals_data_version;
+  if (trusted_bidding_signals_result) {
     bidding_signals_data_version =
         trusted_bidding_signals_result->GetDataVersion();
   }
-  args.push_back(trusted_signals);
 
   v8::Local<v8::Object> browser_signals = v8::Object::New(isolate);
   gin::Dictionary browser_signals_dict(isolate, browser_signals);
@@ -1255,13 +1258,7 @@
   }
 
   task->trusted_bidding_signals_error_msg = std::move(error_msg);
-  // Only hold onto `result` if it has information that needs to be passed to
-  // generateBid().
-  if (task->bidder_worklet_non_shared_params->trusted_bidding_signals_keys &&
-      !task->bidder_worklet_non_shared_params->trusted_bidding_signals_keys
-           ->empty()) {
-    task->trusted_bidding_signals_result = std::move(result);
-  }
+  task->trusted_bidding_signals_result = std::move(result);
   task->trusted_bidding_signals_request.reset();
 
   // Deleting `generate_bid_task` will destroy `generate_bid_client` and thus
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 877c955f..e69f78a 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -3652,7 +3652,24 @@
           R"("ad")", 7, /*ad_cost=*/absl::nullopt,
           blink::AdDescriptor(GURL("https://response.test/")),
           /*ad_component_descriptors=*/absl::nullopt, base::TimeDelta()),
-      7u);
+      /*expected_data_version=*/7u);
+}
+
+// Even with no trustedBiddingSignalsKeys, the data version should be available.
+TEST_F(BidderWorkletTest, GenerateBidDataVersionNoKeys) {
+  interest_group_trusted_bidding_signals_url_ = GURL("https://signals.test/");
+  AddBidderJsonResponse(
+      &url_loader_factory_,
+      GURL("https://signals.test/"
+           "?hostname=top.window.test&interestGroupNames=Fred"),
+      R"({})", /*data_version=*/7u);
+  RunGenerateBidWithReturnValueExpectingResult(
+      R"({ad: "ad", bid:browserSignals.dataVersion, render:"https://response.test/"})",
+      mojom::BidderWorkletBid::New(
+          R"("ad")", 7, /*ad_cost=*/absl::nullopt,
+          blink::AdDescriptor(GURL("https://response.test/")),
+          /*ad_component_descriptors=*/absl::nullopt, base::TimeDelta()),
+      /*expected_data_version=*/7u);
 }
 
 // Even though the script had set an intermediate result with setBid, the
diff --git a/content/services/auction_worklet/trusted_signals.cc b/content/services/auction_worklet/trusted_signals.cc
index dc1386d..67aa40b 100644
--- a/content/services/auction_worklet/trusted_signals.cc
+++ b/content/services/auction_worklet/trusted_signals.cc
@@ -497,7 +497,11 @@
   v8::Local<v8::Value> v8_data;
   if (!v8_helper->CreateValueFromJson(v8_helper->scratch_context(), *body)
            .ToLocal(&v8_data) ||
-      !v8_data->IsObject()) {
+      !v8_data->IsObject() ||
+      // v8 considers arrays a subtype of object, but the response body must be
+      // a JSON object, not a JSON array, so need to explicitly check if it's an
+      // array.
+      v8_data->IsArray()) {
     std::string error = base::StrCat(
         {signals_url.spec(), " Unable to parse as a JSON object."});
     PostCallbackToUserThread(std::move(user_thread_task_runner), weak_instance,
diff --git a/content/services/auction_worklet/trusted_signals_unittest.cc b/content/services/auction_worklet/trusted_signals_unittest.cc
index e93ba46..329222ba 100644
--- a/content/services/auction_worklet/trusted_signals_unittest.cc
+++ b/content/services/auction_worklet/trusted_signals_unittest.cc
@@ -312,28 +312,46 @@
       error_msg_.value());
 }
 
-TEST_F(TrustedSignalsTest, BiddingSignalsResponseNotJson) {
-  EXPECT_FALSE(FetchBiddingSignalsWithResponse(
-      GURL("https://url.test/"
-           "?hostname=publisher&keys=key1&interestGroupNames=name1"),
-      "Not Json", {"name1"}, {"key1"}, kHostname,
-      /*experiment_group_id=*/absl::nullopt));
-  ASSERT_TRUE(error_msg_.has_value());
-  EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.",
-            error_msg_.value());
+TEST_F(TrustedSignalsTest, BiddingSignalsResponseNotJsonObject) {
+  const char* kTestCases[] = {
+      "",     "Not JSON",           "null",
+      "5",    R"("Not an object")", R"(["Also not an object"])",
+      "{} {}"};
+
+  for (const char* test_case : kTestCases) {
+    SCOPED_TRACE(test_case);
+
+    EXPECT_FALSE(FetchBiddingSignalsWithResponse(
+        GURL("https://url.test/"
+             "?hostname=publisher&keys=key1&interestGroupNames=name1"),
+        test_case, {"name1"}, {"key1"}, kHostname,
+        /*experiment_group_id=*/absl::nullopt));
+    ASSERT_TRUE(error_msg_.has_value());
+    EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.",
+              error_msg_.value());
+  }
 }
 
-TEST_F(TrustedSignalsTest, ScoringSignalsResponseNotJson) {
-  EXPECT_FALSE(FetchScoringSignalsWithResponse(
-      GURL("https://url.test/"
-           "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"),
-      "Not Json",
-      /*render_urls=*/{"https://foo.test/"},
-      /*ad_component_render_urls=*/{}, kHostname,
-      /*experiment_group_id=*/absl::nullopt));
-  ASSERT_TRUE(error_msg_.has_value());
-  EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.",
-            error_msg_.value());
+TEST_F(TrustedSignalsTest, ScoringSignalsResponseNotJsonObject) {
+  const char* kTestCases[] = {
+      "",     "Not JSON",           "null",
+      "5",    R"("Not an object")", R"(["Also not an object"])",
+      "{} {}"};
+
+  for (const char* test_case : kTestCases) {
+    SCOPED_TRACE(test_case);
+
+    EXPECT_FALSE(FetchScoringSignalsWithResponse(
+        GURL("https://url.test/"
+             "?hostname=publisher&renderUrls=https%3A%2F%2Ffoo.test%2F"),
+        test_case,
+        /*render_urls=*/{"https://foo.test/"},
+        /*ad_component_render_urls=*/{}, kHostname,
+        /*experiment_group_id=*/absl::nullopt));
+    ASSERT_TRUE(error_msg_.has_value());
+    EXPECT_EQ("https://url.test/ Unable to parse as a JSON object.",
+              error_msg_.value());
+  }
 }
 
 TEST_F(TrustedSignalsTest, BiddingSignalsInvalidVersion) {
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index ddccc12..35f8eeb 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -220,6 +220,9 @@
 [ mac ] Pixel_MediaFoundationClearDirectComposition [ Skip ]
 [ win8 ] Pixel_MediaFoundationClearDirectComposition [ Skip ]
 
+# WebGPU is not shipped on MacOS < 13.
+crbug.com/1426664 [ monterey ] Pixel_WebGPU* [ Skip ]
+
 ###############################
 # Temporary Skip Expectations #
 ###############################
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 4975d35..6158138 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -221,10 +221,13 @@
 
 # Seems to do enough JavaScript work that heartbeats are not reliably sent.
 [ android android-pixel-4 passthrough ] conformance/extensions/webgl-compressed-texture-astc.html [ Slow ]
-[ chromeos chromeos-board-amd64-generic passthrough ] conformance/extensions/s3tc-and-rgtc.html [ Slow ]
-[ chromeos chromeos-board-amd64-generic passthrough ] conformance/extensions/webgl-compressed-texture-astc.html [ Slow ]
-[ chromeos chromeos-board-amd64-generic passthrough ] conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html [ Slow ]
-[ chromeos chromeos-board-amd64-generic passthrough ] conformance2/sync/sync-webgl-specific.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance/extensions/webgl-compressed-texture-astc.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance/extensions/s3tc-and-rgtc.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance/textures/canvas_sub_rectangle/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/textures/canvas_sub_rectangle/tex-2d-rg32f-rg-float.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/textures/canvas_sub_rectangle/tex-2d-rg8ui-rg_integer-unsigned_byte.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/uniforms/incompatible-texture-type-for-sampler.html [ Slow ]
 
 ###################
 # Failures/Flakes #
@@ -653,6 +656,12 @@
 # Failing test in passthrough mode when experiment DrDc is enabled via fieldtrial_testing_config.json.
 crbug.com/1276552 [ android passthrough ] conformance/canvas/render-after-resize-test.html [ Failure ]
 
+# finder:group-start crbug.com/1426535 Flaky DCHECK on Pixel 4 and 6
+crbug.com/1426535 [ android ] conformance/state/gl-enable-enum-test.html [ RetryOnFailure ]
+crbug.com/1426535 [ android ] conformance/state/gl-get-calls.html [ RetryOnFailure ]
+crbug.com/1426535 [ android ] conformance/state/gl-initial-state.html [ RetryOnFailure ]
+# finder:group-end
+
 ## Pixel 2 ##
 
 crbug.com/906742 [ android-pie android-pixel-2 no-passthrough qualcomm ] conformance2/glsl3/compare-structs-containing-arrays.html [ Failure ]
@@ -740,6 +749,8 @@
 crbug.com/1241183 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/textures/misc/immutable-tex-render-feedback.html [ Failure ]
 crbug.com/1399117 [ chromeos chromeos-board-amd64-generic passthrough ] WebglExtension_WEBGL_provoking_vertex [ Skip ]
 
+crbug.com/1426913 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/sync/sync-webgl-specific.html [ RetryOnFailure ]
+
 # Must investigate ChromeOS failures with passthrough command decoder.
 crbug.com/angleproject/5038 [ chromeos passthrough ] conformance/extensions/ext-color-buffer-half-float.html [ Failure ]
 
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 cbb0a63..0c593354 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
@@ -330,6 +330,11 @@
 
 # Seems to do enough JavaScript work that heartbeats are not reliably sent.
 crbug.com/1426593 conformance/uniforms/no-over-optimization-on-uniform-array-* [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic passthrough ] conformance2/misc/uninitialized-test-2.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic no-passthrough ] conformance/extensions/oes-texture-half-float-linear.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic no-passthrough ] conformance/extensions/webgl-compressed-texture-astc.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic no-passthrough ] conformance/reading/read-pixels-test.html [ Slow ]
+crbug.com/1426916 [ chromeos chromeos-board-amd64-generic no-passthrough ] conformance/textures/misc/tex-image-and-sub-image-2d-with-array-buffer-view.html [ Slow ]
 
 ###################
 # Failures/Flakes #
@@ -526,6 +531,12 @@
 crbug.com/1347077 [ android-nexus-5x android-nougat angle-opengles passthrough qualcomm renderer-skia-gl target-cpu-64 ] conformance/textures/misc/video-rotation.html [ Failure ]
 crbug.com/1347077 [ android-s android-sm-a235m angle-disabled no-passthrough qualcomm renderer-skia-gl target-cpu-32 ] conformance/textures/misc/video-rotation.html [ Failure ]
 
+# finder:group-start crbug.com/1426535 Flaky DCHECK on Pixel 4 and 6
+crbug.com/1426535 [ android ] conformance/state/gl-enable-enum-test.html [ RetryOnFailure ]
+crbug.com/1426535 [ android ] conformance/state/gl-get-calls.html [ RetryOnFailure ]
+crbug.com/1426535 [ android ] conformance/state/gl-initial-state.html [ RetryOnFailure ]
+# finder:group-end
+
 ## Nexus 5X ##
 
 # Was timing out randomly on android_optional_gpu_tests_rel, but became so
@@ -578,8 +589,6 @@
 crbug.com/1288590 [ android android-pixel-6 ] conformance/misc/shader-precision-format.html [ Failure ]
 crbug.com/1288603 [ android android-pixel-6 passthrough angle-opengles ] conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
 crbug.com/1357064 [ android android-pixel-6 no-passthrough ] conformance/rendering/blending.html [ Failure ]
-crbug.com/1426535 [ android android-pixel-6 ] conformance/state/gl-enable-enum-test.html [ RetryOnFailure ]
-crbug.com/1426535 [ android android-pixel-6 ] conformance/state/gl-get-calls.html [ RetryOnFailure ]
 
 # finder:group-start crbug.com/1383180 failures happen in many tests, so can be classified as stale for one specific test
 crbug.com/1383180 [ android android-pixel-6 ] conformance/extensions/oes-texture-float-with-video.html [ RetryOnFailure ]
diff --git a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
index a2dbaa53..35c6738 100644
--- a/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
+++ b/content/test/gpu/gpu_tests/webgpu_cts_integration_test.py
@@ -71,6 +71,7 @@
   _enable_dawn_backend_validation = False
   _use_webgpu_adapter = None  # use the default
   _original_environ = None
+  _use_webgpu_power_preference = None
 
   _build_dir = None
 
@@ -187,6 +188,11 @@
         type=str,
         default=None,
         help=('Runs the browser with a particular WebGPU adapter'))
+    parser.add_option(
+        '--use-webgpu-power-preference',
+        type=str,
+        default=None,
+        help=('Runs the browser with a particular WebGPU power preference'))
 
   @classmethod
   def StartBrowser(cls) -> None:
@@ -211,6 +217,9 @@
 
     if cls._use_webgpu_adapter:
       browser_args.append('--use-webgpu-adapter=%s' % cls._use_webgpu_adapter)
+    if cls._use_webgpu_power_preference:
+      browser_args.append('--use-webgpu-power-preference=%s' %
+                          cls._use_webgpu_power_preference)
     if cls._enable_dawn_backend_validation:
       if sys.platform == 'win32':
         browser_args.append('--enable-dawn-backend-validation=partial')
@@ -237,6 +246,7 @@
       cls._test_timeout = options.override_timeout
     cls._enable_dawn_backend_validation = options.enable_dawn_backend_validation
     cls._use_webgpu_adapter = options.use_webgpu_adapter
+    cls._use_webgpu_power_preference = options.use_webgpu_power_preference
 
   @classmethod
   def _ModifyBrowserEnvironment(cls) -> None:
@@ -505,6 +515,8 @@
       tags.append('webgpu-adapter-' + cls._use_webgpu_adapter)
     else:
       tags.append('webgpu-adapter-default')
+    # No need to tag _use_webgpu_power_preference here,
+    # since Telemetry already reports the GPU vendorID
 
     system_info = browser.GetSystemInfo()
     if system_info:
diff --git a/docs/threading_and_tasks.md b/docs/threading_and_tasks.md
index 5ac8269ba..2eae39a7 100644
--- a/docs/threading_and_tasks.md
+++ b/docs/threading_and_tasks.md
@@ -496,12 +496,14 @@
 
 Methods that take `base::TaskTraits` can be be passed `{}` when default traits
 are sufficient. Default traits are appropriate for tasks that:
+
 - Don’t block (ref. MayBlock and WithBaseSyncPrimitives);
 - Pertain to user-blocking activity;
   (explicitly or implicitly by having an ordering dependency with a component
    that does)
 - Can either block shutdown or be skipped on shutdown (thread pool is free to
   choose a fitting default).
+
 Tasks that don’t match this description must be posted with explicit TaskTraits.
 
 [`base/task/task_traits.h`](https://cs.chromium.org/chromium/src/base/task/task_traits.h)
diff --git a/gpu/command_buffer/service/service_utils.cc b/gpu/command_buffer/service/service_utils.cc
index fa30d7f..6de10f3 100644
--- a/gpu/command_buffer/service/service_utils.cc
+++ b/gpu/command_buffer/service/service_utils.cc
@@ -160,6 +160,8 @@
   gpu_preferences.enable_unsafe_webgpu =
       command_line->HasSwitch(switches::kEnableUnsafeWebGPU);
   gpu_preferences.use_webgpu_adapter = ParseWebGPUAdapterName(command_line);
+  gpu_preferences.use_webgpu_power_preference =
+      ParseWebGPUPowerPreference(command_line);
   if (command_line->HasSwitch(switches::kEnableDawnBackendValidation)) {
     auto value = command_line->GetSwitchValueASCII(
         switches::kEnableDawnBackendValidation);
@@ -275,5 +277,28 @@
   return WebGPUAdapterName::kDefault;
 }
 
+WebGPUPowerPreference ParseWebGPUPowerPreference(
+    const base::CommandLine* command_line) {
+  if (command_line->HasSwitch(switches::kUseWebGPUPowerPreference)) {
+    auto value =
+        command_line->GetSwitchValueASCII(switches::kUseWebGPUPowerPreference);
+    if (value.empty()) {
+      return WebGPUPowerPreference::kDefaultLowPower;
+    } else if (value == "default-low-power") {
+      return WebGPUPowerPreference::kDefaultLowPower;
+    } else if (value == "default-high-performance") {
+      return WebGPUPowerPreference::kDefaultHighPerformance;
+    } else if (value == "force-low-power") {
+      return WebGPUPowerPreference::kForceLowPower;
+    } else if (value == "force-high-performance") {
+      return WebGPUPowerPreference::kForceHighPerformance;
+    } else {
+      DLOG(ERROR) << "Invalid switch " << switches::kUseWebGPUPowerPreference
+                  << "=" << value << ".";
+    }
+  }
+  return WebGPUPowerPreference::kDefaultLowPower;
+}
+
 }  // namespace gles2
 }  // namespace gpu
diff --git a/gpu/command_buffer/service/service_utils.h b/gpu/command_buffer/service/service_utils.h
index dbecb09e..1462fff 100644
--- a/gpu/command_buffer/service/service_utils.h
+++ b/gpu/command_buffer/service/service_utils.h
@@ -48,6 +48,9 @@
 GPU_GLES2_EXPORT WebGPUAdapterName
 ParseWebGPUAdapterName(const base::CommandLine* command_line);
 
+GPU_GLES2_EXPORT WebGPUPowerPreference
+ParseWebGPUPowerPreference(const base::CommandLine* command_line);
+
 }  // namespace gles2
 }  // namespace gpu
 
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index d35879f..8e85243 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -81,11 +81,12 @@
     WGPUPowerPreference power_preference) {
   switch (power_preference) {
     case WGPUPowerPreference_LowPower:
+    // Currently for simplicity we always choose integrated GPU as the device
+    // related to default power preference. This avoids websites starting
+    // WebGPU just for feature detection from powering up the discrete GPU.
+    case WGPUPowerPreference_Undefined:
       return WGPUAdapterType_IntegratedGPU;
     case WGPUPowerPreference_HighPerformance:
-    // Currently for simplicity we always choose discrete GPU as the device
-    // related to default power preference.
-    case WGPUPowerPreference_Undefined:
       return WGPUAdapterType_DiscreteGPU;
     default:
       NOTREACHED();
@@ -437,6 +438,8 @@
 
   bool enable_unsafe_webgpu_ = false;
   WebGPUAdapterName use_webgpu_adapter_ = WebGPUAdapterName::kDefault;
+  WebGPUPowerPreference use_webgpu_power_preference_ =
+      WebGPUPowerPreference::kDefaultLowPower;
   std::vector<std::string> require_enabled_toggles_;
   std::vector<std::string> require_disabled_toggles_;
   bool allow_unsafe_apis_;
@@ -1098,6 +1101,7 @@
       isolation_key_provider_(isolation_key_provider) {
   enable_unsafe_webgpu_ = gpu_preferences.enable_unsafe_webgpu;
   use_webgpu_adapter_ = gpu_preferences.use_webgpu_adapter;
+  use_webgpu_power_preference_ = gpu_preferences.use_webgpu_power_preference;
   require_enabled_toggles_ = gpu_preferences.enabled_dawn_features_list;
   require_disabled_toggles_ = gpu_preferences.disabled_dawn_features_list;
 
@@ -1571,8 +1575,37 @@
 int32_t WebGPUDecoderImpl::GetPreferredAdapterIndex(
     WGPUPowerPreference power_preference,
     bool force_fallback) const {
-  WGPUAdapterType preferred_adapter_type =
-      PowerPreferenceToDawnAdapterType(power_preference);
+  WGPUAdapterType preferred_adapter_type;
+  if (use_webgpu_adapter_ == WebGPUAdapterName::kSwiftShader) {
+    // When it is using SwiftShader, it is using CPU, so ignore webgpu power
+    // preference flag.
+    DCHECK(force_fallback);
+    preferred_adapter_type = WGPUAdapterType_CPU;
+  } else {
+    WGPUPowerPreference adjusted_power_preference = power_preference;
+    switch (use_webgpu_power_preference_) {
+      case WebGPUPowerPreference::kDefaultLowPower:
+        if (adjusted_power_preference == WGPUPowerPreference_Undefined) {
+          adjusted_power_preference = WGPUPowerPreference_LowPower;
+        }
+        break;
+      case WebGPUPowerPreference::kDefaultHighPerformance:
+        if (adjusted_power_preference == WGPUPowerPreference_Undefined) {
+          adjusted_power_preference = WGPUPowerPreference_HighPerformance;
+        }
+        break;
+      case WebGPUPowerPreference::kForceLowPower:
+        adjusted_power_preference = WGPUPowerPreference_LowPower;
+        break;
+      case WebGPUPowerPreference::kForceHighPerformance:
+        adjusted_power_preference = WGPUPowerPreference_HighPerformance;
+        break;
+      default:
+        break;
+    }
+    preferred_adapter_type =
+        PowerPreferenceToDawnAdapterType(adjusted_power_preference);
+  }
 
   int32_t discrete_gpu_adapter_index = -1;
   int32_t integrated_gpu_adapter_index = -1;
@@ -1612,12 +1645,22 @@
     }
   }
 
-  // For now, we always prefer the discrete GPU
-  if (discrete_gpu_adapter_index >= 0) {
+  // For now, we always prefer the integrated GPU
+  if (integrated_gpu_adapter_index >= 0 &&
+      use_webgpu_power_preference_ !=
+          WebGPUPowerPreference::kForceHighPerformance) {
+    return integrated_gpu_adapter_index;
+  }
+  if (discrete_gpu_adapter_index >= 0 &&
+      use_webgpu_power_preference_ != WebGPUPowerPreference::kForceLowPower) {
     return discrete_gpu_adapter_index;
   }
-  if (integrated_gpu_adapter_index >= 0) {
-    return integrated_gpu_adapter_index;
+  if (use_webgpu_power_preference_ == WebGPUPowerPreference::kForceLowPower ||
+      use_webgpu_power_preference_ ==
+          WebGPUPowerPreference::kForceHighPerformance) {
+    // If we cannot find the forced adapter type, early return here instead of
+    // returning any other adapter.
+    return -1;
   }
   if (cpu_adapter_index >= 0) {
     return cpu_adapter_index;
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index a2fd0a1..3a5d3f4 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -146,7 +146,8 @@
 // Enables the use of out of process rasterization for canvas.
 BASE_FEATURE(kCanvasOopRasterization,
              "CanvasOopRasterization",
-#if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_IOS)
+#if BUILDFLAG(IS_FUCHSIA) || BUILDFLAG(IS_IOS) || BUILDFLAG(IS_WIN) || \
+    (BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64))
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
@@ -184,6 +185,14 @@
 BASE_FEATURE(kNoUndamagedOverlayPromotion,
              "NoUndamagedOverlayPromotion",
              base::FEATURE_DISABLED_BY_DEFAULT);
+
+// Use a DCompPresenter as the root surface, instead of a
+// DirectCompositionSurfaceWin. DCompPresenter is surface-less and the actual
+// allocation of the root surface will be owned by the
+// SkiaOutputDeviceDCompPresenter.
+BASE_FEATURE(kDCompPresenter,
+             "DCompPresenter",
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_IOS)
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index 67f2923..e667e4a3 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -44,6 +44,8 @@
 GPU_EXPORT BASE_DECLARE_FEATURE(kDisableVideoOverlayIfMoving);
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kNoUndamagedOverlayPromotion);
+
+GPU_EXPORT BASE_DECLARE_FEATURE(kDCompPresenter);
 #endif
 
 #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_IOS)
diff --git a/gpu/config/gpu_preferences.h b/gpu/config/gpu_preferences.h
index 5a41c0d..cd95590 100644
--- a/gpu/config/gpu_preferences.h
+++ b/gpu/config/gpu_preferences.h
@@ -45,6 +45,19 @@
   kSwiftShader = 2,
 };
 
+// Affecting how chromium handles GPUPowerPreference in
+// GPURequestAdapterOptions.
+enum class WebGPUPowerPreference : uint32_t {
+  // Choose the preferred adapter when GPUPowerPreference is not given.
+  // Has no impact when GPUPowerPreference is given.
+  kDefaultLowPower = 0,
+  kDefaultHighPerformance = 1,
+  // Choose the forced adapter regardless of whether GPUPowerPreference is set
+  // or not.
+  kForceLowPower = 2,
+  kForceHighPerformance = 3,
+};
+
 enum class GrContextType : uint32_t {
   kGL = 0,
   kVulkan = 1,
@@ -253,6 +266,10 @@
   // The adapter to use for WebGPU content.
   WebGPUAdapterName use_webgpu_adapter = WebGPUAdapterName::kDefault;
 
+  // The adapter selecting strategy related to GPUPowerPreference.
+  WebGPUPowerPreference use_webgpu_power_preference =
+      WebGPUPowerPreference::kDefaultHighPerformance;
+
   // The Dawn features(toggles) enabled on the creation of Dawn devices.
   std::vector<std::string> enabled_dawn_features_list;
 
diff --git a/gpu/config/gpu_switches.cc b/gpu/config/gpu_switches.cc
index 21872a1..939598df 100644
--- a/gpu/config/gpu_switches.cc
+++ b/gpu/config/gpu_switches.cc
@@ -54,6 +54,10 @@
 // The adapter to use for WebGPU content.
 GPU_EXPORT extern const char kUseWebGPUAdapter[] = "use-webgpu-adapter";
 
+// The adapter selecting strategy related to GPUPowerPreference.
+GPU_EXPORT extern const char kUseWebGPUPowerPreference[] =
+    "use-webgpu-power-preference";
+
 // Set the Dawn features(toggles) enabled on the creation of Dawn devices.
 const char kEnableDawnFeatures[] = "enable-dawn-features";
 
diff --git a/gpu/config/gpu_switches.h b/gpu/config/gpu_switches.h
index 4970fcd..4b2237d 100644
--- a/gpu/config/gpu_switches.h
+++ b/gpu/config/gpu_switches.h
@@ -22,6 +22,7 @@
 GPU_EXPORT extern const char kEnableWebGPUDeveloperFeatures[];
 GPU_EXPORT extern const char kEnableDawnBackendValidation[];
 GPU_EXPORT extern const char kUseWebGPUAdapter[];
+GPU_EXPORT extern const char kUseWebGPUPowerPreference[];
 GPU_EXPORT extern const char kEnableDawnFeatures[];
 GPU_EXPORT extern const char kDisableDawnFeatures[];
 GPU_EXPORT extern const char kUseHighGPUThreadPriorityForPerfTests[];
diff --git a/gpu/ipc/common/gpu_preferences.mojom b/gpu/ipc/common/gpu_preferences.mojom
index 41dbfba..3f8a4563 100644
--- a/gpu/ipc/common/gpu_preferences.mojom
+++ b/gpu/ipc/common/gpu_preferences.mojom
@@ -26,6 +26,14 @@
   kSwiftShader = 2,
 };
 
+// Corresponds to gpu::WebGPUPowerPreference.
+enum WebGPUPowerPreference {
+  kDefaultLowPower = 0,
+  kDefaultHighPerformance = 1,
+  kForceLowPower = 2,
+  kForceHighPerformance = 3,
+};
+
 // Corresponds to gpu::GrContextType.
 enum GrContextType {
   kGL = 0,
@@ -96,6 +104,7 @@
   bool enable_webgpu;
   bool enable_unsafe_webgpu;
   WebGPUAdapterName use_webgpu_adapter;
+  WebGPUPowerPreference use_webgpu_power_preference;
   DawnBackendValidationLevel enable_dawn_backend_validation;
   array<string> enabled_dawn_features_list;
   array<string> disabled_dawn_features_list;
diff --git a/gpu/ipc/common/gpu_preferences_mojom_traits.h b/gpu/ipc/common/gpu_preferences_mojom_traits.h
index 4bcb75d..fef1069 100644
--- a/gpu/ipc/common/gpu_preferences_mojom_traits.h
+++ b/gpu/ipc/common/gpu_preferences_mojom_traits.h
@@ -129,6 +129,45 @@
 };
 
 template <>
+struct GPU_EXPORT
+    EnumTraits<gpu::mojom::WebGPUPowerPreference, gpu::WebGPUPowerPreference> {
+  static gpu::mojom::WebGPUPowerPreference ToMojom(
+      gpu::WebGPUPowerPreference input) {
+    switch (input) {
+      case gpu::WebGPUPowerPreference::kDefaultLowPower:
+        return gpu::mojom::WebGPUPowerPreference::kDefaultLowPower;
+      case gpu::WebGPUPowerPreference::kDefaultHighPerformance:
+        return gpu::mojom::WebGPUPowerPreference::kDefaultHighPerformance;
+      case gpu::WebGPUPowerPreference::kForceLowPower:
+        return gpu::mojom::WebGPUPowerPreference::kForceLowPower;
+      case gpu::WebGPUPowerPreference::kForceHighPerformance:
+        return gpu::mojom::WebGPUPowerPreference::kForceHighPerformance;
+    }
+    NOTREACHED();
+    return gpu::mojom::WebGPUPowerPreference::kDefaultHighPerformance;
+  }
+
+  static bool FromMojom(gpu::mojom::WebGPUPowerPreference input,
+                        gpu::WebGPUPowerPreference* out) {
+    switch (input) {
+      case gpu::mojom::WebGPUPowerPreference::kDefaultLowPower:
+        *out = gpu::WebGPUPowerPreference::kDefaultLowPower;
+        return true;
+      case gpu::mojom::WebGPUPowerPreference::kDefaultHighPerformance:
+        *out = gpu::WebGPUPowerPreference::kDefaultHighPerformance;
+        return true;
+      case gpu::mojom::WebGPUPowerPreference::kForceLowPower:
+        *out = gpu::WebGPUPowerPreference::kForceLowPower;
+        return true;
+      case gpu::mojom::WebGPUPowerPreference::kForceHighPerformance:
+        *out = gpu::WebGPUPowerPreference::kForceHighPerformance;
+        return true;
+    }
+    return false;
+  }
+};
+
+template <>
 struct GPU_EXPORT EnumTraits<gpu::mojom::DawnBackendValidationLevel,
                              gpu::DawnBackendValidationLevel> {
   static gpu::mojom::DawnBackendValidationLevel ToMojom(
@@ -239,6 +278,10 @@
     out->enable_unsafe_webgpu = prefs.enable_unsafe_webgpu();
     if (!prefs.ReadUseWebgpuAdapter(&out->use_webgpu_adapter))
       return false;
+    if (!prefs.ReadUseWebgpuPowerPreference(
+            &out->use_webgpu_power_preference)) {
+      return false;
+    }
     if (!prefs.ReadEnableDawnBackendValidation(
             &out->enable_dawn_backend_validation))
       return false;
@@ -424,6 +467,10 @@
       const gpu::GpuPreferences& prefs) {
     return prefs.use_webgpu_adapter;
   }
+  static gpu::WebGPUPowerPreference use_webgpu_power_preference(
+      const gpu::GpuPreferences& prefs) {
+    return prefs.use_webgpu_power_preference;
+  }
   static gpu::DawnBackendValidationLevel enable_dawn_backend_validation(
       const gpu::GpuPreferences& prefs) {
     return prefs.enable_dawn_backend_validation;
diff --git a/gpu/ipc/service/image_transport_surface_win.cc b/gpu/ipc/service/image_transport_surface_win.cc
index 61b4a2d..ba98ffe 100644
--- a/gpu/ipc/service/image_transport_surface_win.cc
+++ b/gpu/ipc/service/image_transport_surface_win.cc
@@ -8,6 +8,7 @@
 
 #include "base/win/windows_version.h"
 #include "gpu/command_buffer/service/feature_info.h"
+#include "gpu/config/gpu_finch_features.h"
 #include "gpu/config/gpu_preferences.h"
 #include "gpu/ipc/service/pass_through_image_transport_surface.h"
 #include "ui/gfx/native_widget_types.h"
@@ -24,14 +25,6 @@
 
 namespace gpu {
 namespace {
-// Use a DCompPresenter as the root surface, instead of a
-// DirectCompositionSurfaceWin. DCompPresenter is surface-less and the actual
-// allocation of the root surface will be owned by the
-// SkiaOutputDeviceDCompPresenter.
-BASE_FEATURE(kDCompPresenter,
-             "DCompPresenter",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 gl::DirectCompositionSurfaceWin::Settings
 CreateDirectCompositionSurfaceSettings(
     const GpuDriverBugWorkarounds& workarounds) {
@@ -55,7 +48,7 @@
     SurfaceHandle surface_handle,
     gl::GLSurfaceFormat format) {
   if (gl::DirectCompositionSupported() &&
-      base::FeatureList::IsEnabled(kDCompPresenter)) {
+      base::FeatureList::IsEnabled(features::kDCompPresenter)) {
     auto vsync_callback = delegate->GetGpuVSyncCallback();
     auto settings = CreateDirectCompositionSurfaceSettings(
         delegate->GetFeatureInfo()->workarounds());
diff --git a/infra/config/dev/subprojects/chromium/ci.star b/infra/config/dev/subprojects/chromium/ci.star
index 4bab0fc7..e8f9e59 100644
--- a/infra/config/dev/subprojects/chromium/ci.star
+++ b/infra/config/dev/subprojects/chromium/ci.star
@@ -86,6 +86,12 @@
         **kwargs
     )
 
+###############################################################################
+# NOTE: If you change any of the following builders, please make sure the
+# GCE image roller that watches these builders is similarly up-to-date. See
+# http://shortn/_F1oktuhGEV.
+###############################################################################
+
 ci_builder(
     name = "android-pie-arm64-rel-swarming",
     builder_spec = builder_config.builder_spec(
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index 4cb6c98f..dcff76b 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -1907,6 +1907,12 @@
       <message name="IDS_IOS_TRACKING_PRICE_MOBILE_NOTIFICATIONS_TITLE" desc="Title for Tracking Price mobile notifications row.">
         Mobile Notifications
       </message>
+      <message name="IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_TITLE" desc="Title for Tracking Price email notifications row.">
+        Email Notifications
+      </message>
+      <message name="IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS" desc="Subtitle that describes to which email address price tracking notifications would be sent.">
+        Send to <ph name="USER_EMAIL">$1<ex>janedoe@google.com</ex></ph>
+      </message>
       <message name="IDS_IOS_PRIVACY_SAFE_BROWSING_ENHANCED_PROTECTION_SUMMARY" desc="Summary for Privacy Safe Browsing enhanced protection mode.">
         Faster, proactive protection against dangerous websites, downloads, and extensions. Warns you about password breaches. Requires browsing data to be sent to Google.
       </message>
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS.png.sha1
new file mode 100644
index 0000000..648d962
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS.png.sha1
@@ -0,0 +1 @@
+753e045867d15f48c2710531bd45e582cdf41d26
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_TITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_TITLE.png.sha1
new file mode 100644
index 0000000..eee7e4f
--- /dev/null
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_TITLE.png.sha1
@@ -0,0 +1 @@
+005d385f00153c0bae6a226defd9bb429d25b9ae
\ No newline at end of file
diff --git a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler_unittest.mm b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler_unittest.mm
index fb80d48..ef8dcd2 100644
--- a/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler_unittest.mm
+++ b/ios/chrome/browser/infobars/overlays/browser_agent/interaction_handlers/passwords/password_infobar_banner_interaction_handler_unittest.mm
@@ -95,11 +95,15 @@
 TEST_F(PasswordInfobarBannerInteractionHandlerTest,
        MainButtonTriggersCredentialProviderPromo) {
   base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(kCredentialProviderExtensionPromo);
+  feature_list.InitAndEnableFeatureWithParameters(
+      kCredentialProviderExtensionPromo,
+      {{"enable_promo_on_password_saved", "true"}});
 
   [[mock_credential_provider_promo_commands_handler_ expect]
       showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
                                                  PasswordSaved];
 
   handler_.MainButtonTapped(infobar_);
+
+  [mock_credential_provider_promo_commands_handler_ verify];
 }
diff --git a/ios/chrome/browser/ntp/features.h b/ios/chrome/browser/ntp/features.h
index 614bc42..c9ba7d9 100644
--- a/ios/chrome/browser/ntp/features.h
+++ b/ios/chrome/browser/ntp/features.h
@@ -9,6 +9,17 @@
 
 #include "base/feature_list.h"
 
+// Engagement criteria type for a feed refresh.
+enum class FeedRefreshEngagementCriteriaType {
+  // Any scroll or interaction.
+  kSimpleEngagement = 0,
+  // Meets minimum scroll criteria or any interaction.
+  kEngagement = 1,
+  // Meets good visit criteria.
+  kGoodVisit = 2,
+  kMaxValue = kGoodVisit,
+};
+
 // Feature flag to enable feed background refresh.
 // Use IsFeedBackgroundRefreshEnabled() instead of this constant directly.
 BASE_DECLARE_FEATURE(kEnableFeedBackgroundRefresh);
@@ -77,6 +88,10 @@
 extern const char kEnableFeedAppCloseBackgroundRefresh[];
 
 // Feature param under `kEnableFeedInvisibleForegroundRefresh` for the
+// engagement criteria type to refresh the feed.
+extern const char kFeedRefreshEngagementCriteriaType[];
+
+// Feature param under `kEnableFeedInvisibleForegroundRefresh` for the
 // background refresh interval in seconds.
 extern const char kAppCloseBackgroundRefreshIntervalInSeconds[];
 
@@ -162,6 +177,9 @@
 // backgrounded.
 bool IsFeedAppCloseBackgroundRefreshEnabled();
 
+// Returns the engagement criteria type for a feed refresh.
+FeedRefreshEngagementCriteriaType GetFeedRefreshEngagementCriteriaType();
+
 // The earliest interval to refresh in the background after app enters the
 // background in app close background refresh.
 double GetAppCloseBackgroundRefreshIntervalInSeconds();
diff --git a/ios/chrome/browser/ntp/features.mm b/ios/chrome/browser/ntp/features.mm
index c6dc3e2d..1da565a 100644
--- a/ios/chrome/browser/ntp/features.mm
+++ b/ios/chrome/browser/ntp/features.mm
@@ -79,6 +79,8 @@
     "EnableFeedAppCloseForegroundRefresh";
 const char kEnableFeedAppCloseBackgroundRefresh[] =
     "EnableFeedAppCloseBackgroundRefresh";
+const char kFeedRefreshEngagementCriteriaType[] =
+    "FeedRefreshEngagementCriteriaType";
 const char kAppCloseBackgroundRefreshIntervalInSeconds[] =
     "AppCloseBackgroundRefreshIntervalInSeconds";
 const char kFeedRefreshTimerTimeoutInSeconds[] =
@@ -226,6 +228,15 @@
       /*default=*/false);
 }
 
+FeedRefreshEngagementCriteriaType GetFeedRefreshEngagementCriteriaType() {
+  return (FeedRefreshEngagementCriteriaType)
+      base::GetFieldTrialParamByFeatureAsInt(
+          kEnableFeedInvisibleForegroundRefresh,
+          kFeedRefreshEngagementCriteriaType,
+          /*default_value=*/
+          (int)FeedRefreshEngagementCriteriaType::kSimpleEngagement);
+}
+
 double GetAppCloseBackgroundRefreshIntervalInSeconds() {
   return base::GetFieldTrialParamByFeatureAsDouble(
       kEnableFeedInvisibleForegroundRefresh,
diff --git a/ios/chrome/browser/passwords/BUILD.gn b/ios/chrome/browser/passwords/BUILD.gn
index 26fb1ea..e7acad1 100644
--- a/ios/chrome/browser/passwords/BUILD.gn
+++ b/ios/chrome/browser/passwords/BUILD.gn
@@ -216,6 +216,8 @@
     "//components/ukm:test_support",
     "//ios/chrome/browser/autofill",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/credential_provider_promo:features",
+    "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/passwords/test",
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/ui/autofill/form_input_accessory",
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client_unittest.mm b/ios/chrome/browser/passwords/ios_chrome_password_manager_client_unittest.mm
index 44fb4fd3..dbae72b 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client_unittest.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client_unittest.mm
@@ -8,13 +8,22 @@
 
 #import <memory>
 
+#import "base/test/scoped_feature_list.h"
+#import "components/password_manager/core/browser/password_form.h"
+
 #import "components/autofill/ios/form_util/unique_id_data_tab_helper.h"
+#import "components/password_manager/core/browser/mock_password_form_manager_for_ui.h"
 #import "components/password_manager/core/browser/mock_password_store_interface.h"
 #import "components/password_manager/core/browser/password_form_manager.h"
+#import "components/password_manager/core/browser/password_form_manager_for_ui.h"
 #import "components/password_manager/core/common/password_manager_pref_names.h"
 #import "components/prefs/testing_pref_service.h"
 #import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/credential_provider_promo/features.h"
+#import "ios/chrome/browser/main/test_browser.h"
 #import "ios/chrome/browser/passwords/password_controller.h"
+#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
+#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
 #import "ios/chrome/browser/web/chrome_web_client.h"
 #import "ios/web/public/test/scoped_testing_web_client.h"
 #import "ios/web/public/test/web_state_test_util.h"
@@ -22,15 +31,19 @@
 #import "testing/gmock/include/gmock/gmock.h"
 #import "testing/gtest/include/gtest/gtest.h"
 #import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
 #import "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+using password_manager::MockPasswordFormManagerForUI;
 using password_manager::PasswordFormManager;
+using password_manager::PasswordFormManagerForUI;
 using password_manager::PasswordManagerClient;
 using password_manager::prefs::kCredentialsEnableService;
+using testing::NiceMock;
 using testing::Return;
 
 // TODO(crbug.com/958833): this file is initiated because of needing test for
@@ -42,6 +55,7 @@
         store_(new testing::NiceMock<
                password_manager::MockPasswordStoreInterface>()) {
     browser_state_ = TestChromeBrowserState::Builder().Build();
+    browser_ = std::make_unique<TestBrowser>(browser_state_.get());
 
     web::WebState::CreateParams params(browser_state_.get());
     web_state_ = web::WebState::Create(params);
@@ -73,6 +87,7 @@
   web::ScopedTestingWebClient web_client_;
   web::WebTaskEnvironment task_environment_;
   std::unique_ptr<TestChromeBrowserState> browser_state_;
+  std::unique_ptr<Browser> browser_;
   std::unique_ptr<web::WebState> web_state_;
 
   // PasswordController for testing.
@@ -103,3 +118,45 @@
   // should be saved.
   EXPECT_TRUE(client->IsSavingAndFillingEnabled(url));
 }
+
+// Tests that `NotifySuccessfulLoginWithExistingPassword` dispatches
+// `CredentialProviderPromoCommands`.
+TEST_F(IOSChromePasswordManagerClientTest,
+       NotifySuccessfulLoginWithExistingPasswordTest) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      kCredentialProviderExtensionPromo,
+      {{"enable_promo_on_login_with_autofill", "true"}});
+
+  // Create a dispatcher for the client, register the command handler for
+  // `CredentialProviderPromoCommands`
+  id credential_provider_promo_commands_handler_mock =
+      OCMStrictProtocolMock(@protocol(CredentialProviderPromoCommands));
+
+  id dispatcher = [[CommandDispatcher alloc] init];
+  [dispatcher
+      startDispatchingToTarget:credential_provider_promo_commands_handler_mock
+                   forProtocol:@protocol(CredentialProviderPromoCommands)];
+  passwordController_.dispatcher = dispatcher;
+
+  // Expect the call with correct trigger type.
+  [[credential_provider_promo_commands_handler_mock expect]
+      showCredentialProviderPromoWithTrigger:
+          CredentialProviderPromoTrigger::SuccessfulLoginUsingExistingPassword];
+
+  // Set up the param for the `NotifySuccessfulLoginWithExistingPassword` call.
+  password_manager::PasswordForm form;
+  auto manager = std::make_unique<NiceMock<MockPasswordFormManagerForUI>>();
+  ON_CALL(*manager, GetPendingCredentials)
+      .WillByDefault(testing::ReturnRef(form));
+  ON_CALL(*manager, IsMovableToAccountStore).WillByDefault(Return(true));
+
+  // Call the tested function.
+  (passwordController_.passwordManagerClient)
+      ->NotifySuccessfulLoginWithExistingPassword(std::move(manager));
+
+  // Verify.
+  [credential_provider_promo_commands_handler_mock verify];
+
+  passwordController_.dispatcher = nil;
+}
diff --git a/ios/chrome/browser/ui/ntp/metrics/BUILD.gn b/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
index 96e04f3..40917b4 100644
--- a/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/metrics/BUILD.gn
@@ -10,10 +10,10 @@
     "feed_metrics_recorder+testing.h",
     "feed_metrics_recorder.h",
     "feed_metrics_recorder.mm",
+    "feed_refresh_state_tracker.h",
     "feed_session_recorder+testing.h",
     "feed_session_recorder.h",
     "feed_session_recorder.mm",
-    "feed_state_tracker.h",
     "metrics.h",
     "metrics.mm",
   ]
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
index de799cfb..4ff12159 100644
--- a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.h
@@ -10,7 +10,7 @@
 #import "base/time/time.h"
 #import "ios/chrome/browser/discover_feed/feed_constants.h"
 #import "ios/chrome/browser/ui/ntp/metrics/feed_metrics_constants.h"
-#import "ios/chrome/browser/ui/ntp/metrics/feed_state_tracker.h"
+#import "ios/chrome/browser/ui/ntp/metrics/feed_refresh_state_tracker.h"
 
 class DiscoverFeedRefresher;
 @protocol FeedControlDelegate;
@@ -21,7 +21,7 @@
 }
 
 // Records different metrics for the NTP feeds.
-@interface FeedMetricsRecorder : NSObject <FeedStateTracker>
+@interface FeedMetricsRecorder : NSObject <FeedRefreshStateTracker>
 
 // Delegate to get the currently selected feed.
 @property(nonatomic, weak) id<FeedControlDelegate> feedControlDelegate;
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
index bfac4b4..ef72fa0 100644
--- a/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_metrics_recorder.mm
@@ -649,7 +649,7 @@
   }
 }
 
-#pragma mark - FeedStateTracker
+#pragma mark - FeedRefreshStateTracker
 
 - (BOOL)isNTPAndFeedVisible {
   return self.isNTPVisible && self.isFeedVisible;
@@ -893,7 +893,10 @@
   // Chrome run.
   if (scrollDistance > 0 || interacted) {
     [self recordEngagedSimple];
-    self.engagedWithLatestRefreshedContent = YES;
+    if (GetFeedRefreshEngagementCriteriaType() ==
+        FeedRefreshEngagementCriteriaType::kSimpleEngagement) {
+      self.engagedWithLatestRefreshedContent = YES;
+    }
   }
 
   // Report the user as engaged if they have scrolled more than the threshold or
@@ -901,14 +904,18 @@
   // Chrome run.
   if (scrollDistance > kMinScrollThreshold || interacted) {
     [self recordEngaged];
+    if (GetFeedRefreshEngagementCriteriaType() ==
+        FeedRefreshEngagementCriteriaType::kEngagement) {
+      self.engagedWithLatestRefreshedContent = YES;
+    }
   }
 
   [self.sessionRecorder recordUserInteractionOrScrolling];
 
-  // This must be called after memoizing if the current session has met
-  // engagement criteria. For example, setting `engagedSimpleReportedDiscover`
-  // must happen before this call.
-  if (IsFeedSessionCloseForegroundRefreshEnabled()) {
+  // This must be called after setting `engagedWithLatestRefreshedContent`
+  // properly after scrolling or interactions.
+  if (IsFeedSessionCloseForegroundRefreshEnabled() &&
+      [self hasEngagedWithLatestRefreshedContent]) {
     [self setOrExtendRefreshTimer];
   }
 }
@@ -1095,6 +1102,10 @@
     UMA_HISTOGRAM_ENUMERATION(kDiscoverFeedEngagementTypeHistogram,
                               FeedEngagementType::kGoodVisit);
     self.goodVisitReportedDiscover = YES;
+    if (GetFeedRefreshEngagementCriteriaType() ==
+        FeedRefreshEngagementCriteriaType::kGoodVisit) {
+      self.engagedWithLatestRefreshedContent = YES;
+    }
   }
 
   // Log interaction for Following feed.
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_refresh_state_tracker.h b/ios/chrome/browser/ui/ntp/metrics/feed_refresh_state_tracker.h
new file mode 100644
index 0000000..17ae98b
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/metrics/feed_refresh_state_tracker.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_UI_NTP_METRICS_FEED_REFRESH_STATE_TRACKER_H_
+#define IOS_CHROME_BROWSER_UI_NTP_METRICS_FEED_REFRESH_STATE_TRACKER_H_
+
+// Tracks state of the feed with regards to refreshing the feed, such as whether
+// the feed is user visible, or if the user has engaged with the latest refresh
+// content.
+@protocol FeedRefreshStateTracker
+
+// Returns YES if the user has engaged with the latest refreshed content. The
+// term "engaged" is an implementation detail of the receiver.
+- (BOOL)hasEngagedWithLatestRefreshedContent;
+
+// Returns YES if the NTP and feed is visible to the user. Returns NO if the NTP
+// is not visible or if the feed is toggled off in the feed header menu.
+- (BOOL)isNTPAndFeedVisible;
+
+@end
+
+#endif  // IOS_CHROME_BROWSER_UI_NTP_METRICS_FEED_REFRESH_STATE_TRACKER_H_
diff --git a/ios/chrome/browser/ui/ntp/metrics/feed_state_tracker.h b/ios/chrome/browser/ui/ntp/metrics/feed_state_tracker.h
deleted file mode 100644
index bacd075a..0000000
--- a/ios/chrome/browser/ui/ntp/metrics/feed_state_tracker.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_NTP_METRICS_FEED_STATE_TRACKER_H_
-#define IOS_CHROME_BROWSER_UI_NTP_METRICS_FEED_STATE_TRACKER_H_
-
-// Tracks state of the feed, such as whether the feed is user visible, or if the
-// user has engaged with the latest refresh content.
-@protocol FeedStateTracker
-
-// Returns YES if the user has engaged with the latest refreshed content. The
-// term "engaged" is an implementation detail of the receiver.
-- (BOOL)hasEngagedWithLatestRefreshedContent;
-
-// Returns YES if the NTP and feed is visible to the user. Returns NO if the NTP
-// is not visible or if the feed is toggled off in the feed header menu.
-- (BOOL)isNTPAndFeedVisible;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_NTP_METRICS_FEED_STATE_TRACKER_H_
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
index d2c76b1..d4d88ad 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.h
@@ -79,12 +79,6 @@
       absl::optional<AutocompleteMatch> optional_match);
 
   // OmniboxView implementation.
-  void OpenMatch(const AutocompleteMatch& match,
-                 WindowOpenDisposition disposition,
-                 const GURL& alternate_nav_url,
-                 const std::u16string& pasted_text,
-                 size_t selected_line,
-                 base::TimeTicks match_selection_timestamp) override;
   std::u16string GetText() const override;
   void SetWindowTextAndCaretPos(const std::u16string& text,
                                 size_t caret_pos,
diff --git a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
index 5eb07f0..8b902f8e 100644
--- a/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
+++ b/ios/chrome/browser/ui/omnibox/omnibox_view_ios.mm
@@ -69,45 +69,6 @@
 
 OmniboxViewIOS::~OmniboxViewIOS() = default;
 
-void OmniboxViewIOS::OpenMatch(const AutocompleteMatch& match,
-                               WindowOpenDisposition disposition,
-                               const GURL& alternate_nav_url,
-                               const std::u16string& pasted_text,
-                               size_t selected_line,
-                               base::TimeTicks match_selection_timestamp) {
-  // Fill in clipboard matches if they don't have a destination URL.
-  if (match.destination_url.is_empty()) {
-    if (match.type == AutocompleteMatchType::CLIPBOARD_URL) {
-      ClipboardRecentContent* clipboard_recent_content =
-          ClipboardRecentContent::GetInstance();
-      clipboard_recent_content->GetRecentURLFromClipboard(base::BindOnce(
-          &OmniboxViewIOS::OnReceiveClipboardURLForOpenMatch,
-          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
-          pasted_text, selected_line, match_selection_timestamp));
-      return;
-    } else if (match.type == AutocompleteMatchType::CLIPBOARD_TEXT) {
-      ClipboardRecentContent* clipboard_recent_content =
-          ClipboardRecentContent::GetInstance();
-      clipboard_recent_content->GetRecentTextFromClipboard(base::BindOnce(
-          &OmniboxViewIOS::OnReceiveClipboardTextForOpenMatch,
-          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
-          pasted_text, selected_line, match_selection_timestamp));
-      return;
-    } else if (match.type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
-      ClipboardRecentContent* clipboard_recent_content =
-          ClipboardRecentContent::GetInstance();
-      clipboard_recent_content->GetRecentImageFromClipboard(base::BindOnce(
-          &OmniboxViewIOS::OnReceiveClipboardImageForOpenMatch,
-          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
-          pasted_text, selected_line, match_selection_timestamp));
-      return;
-    }
-  }
-
-  OmniboxView::OpenMatch(match, disposition, alternate_nav_url, pasted_text,
-                         selected_line, match_selection_timestamp);
-}
-
 void OmniboxViewIOS::OnReceiveClipboardURLForOpenMatch(
     const AutocompleteMatch& match,
     WindowOpenDisposition disposition,
@@ -122,12 +83,11 @@
 
   GURL url = std::move(optional_gurl).value();
 
-  ClipboardProvider* clipboard_provider =
-      model()->autocomplete_controller()->clipboard_provider();
-  AutocompleteMatch new_match = clipboard_provider->NewClipboardURLMatch(url);
+  AutocompleteController* controller = model()->autocomplete_controller();
 
-  OmniboxView::OpenMatch(new_match, disposition, alternate_nav_url, pasted_text,
-                         selected_line, match_selection_timestamp);
+  OmniboxPopupSelection selection(controller->InjectAdHocMatch(
+      controller->clipboard_provider()->NewClipboardURLMatch(url)));
+  model()->OpenSelection(selection, match_selection_timestamp, disposition);
 }
 
 void OmniboxViewIOS::OnReceiveClipboardTextForOpenMatch(
@@ -153,8 +113,9 @@
     return;
   }
 
-  OmniboxView::OpenMatch(new_match.value(), disposition, alternate_nav_url,
-                         pasted_text, selected_line, match_selection_timestamp);
+  OmniboxPopupSelection selection(
+      model()->autocomplete_controller()->InjectAdHocMatch(new_match.value()));
+  model()->OpenSelection(selection, match_selection_timestamp, disposition);
 }
 
 void OmniboxViewIOS::OnReceiveClipboardImageForOpenMatch(
@@ -185,8 +146,10 @@
   if (!optional_match) {
     return;
   }
-  OmniboxView::OpenMatch(optional_match.value(), disposition, alternate_nav_url,
-                         pasted_text, selected_line, match_selection_timestamp);
+  OmniboxPopupSelection selection(
+      model()->autocomplete_controller()->InjectAdHocMatch(
+          optional_match.value()));
+  model()->OpenSelection(selection, match_selection_timestamp, disposition);
 }
 
 std::u16string OmniboxViewIOS::GetText() const {
@@ -549,9 +512,8 @@
 void OmniboxViewIOS::OnAccept() {
   base::RecordAction(UserMetricsAction("MobileOmniboxUse"));
 
-  WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB;
   if (model()) {
-    model()->AcceptInput(disposition);
+    model()->OpenSelection();
   }
   RevertAll();
 }
@@ -742,6 +704,48 @@
     const GURL& alternate_nav_url,
     const std::u16string& pasted_text,
     size_t index) {
-  this->OpenMatch(match, disposition, alternate_nav_url, pasted_text, index,
-                  base::TimeTicks());
+  const auto match_selection_timestamp = base::TimeTicks();
+  AutocompleteController* controller = model()->autocomplete_controller();
+
+  // Sometimes the match provided does not correspond to the autocomplete
+  // result match specified by `index`. Most Visited Tiles, for example,
+  // provide ad hoc matches that are not in the result at all.
+  if (index >= controller->result().size() ||
+      controller->result().match_at(index).destination_url !=
+          match.destination_url) {
+    OmniboxPopupSelection selection(controller->InjectAdHocMatch(match));
+    model()->OpenSelection(selection, match_selection_timestamp, disposition);
+    return;
+  }
+
+  // Fill in clipboard matches if they don't have a destination URL.
+  if (match.destination_url.is_empty()) {
+    if (match.type == AutocompleteMatchType::CLIPBOARD_URL) {
+      ClipboardRecentContent* clipboard_recent_content =
+          ClipboardRecentContent::GetInstance();
+      clipboard_recent_content->GetRecentURLFromClipboard(base::BindOnce(
+          &OmniboxViewIOS::OnReceiveClipboardURLForOpenMatch,
+          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
+          pasted_text, index, match_selection_timestamp));
+      return;
+    } else if (match.type == AutocompleteMatchType::CLIPBOARD_TEXT) {
+      ClipboardRecentContent* clipboard_recent_content =
+          ClipboardRecentContent::GetInstance();
+      clipboard_recent_content->GetRecentTextFromClipboard(base::BindOnce(
+          &OmniboxViewIOS::OnReceiveClipboardTextForOpenMatch,
+          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
+          pasted_text, index, match_selection_timestamp));
+      return;
+    } else if (match.type == AutocompleteMatchType::CLIPBOARD_IMAGE) {
+      ClipboardRecentContent* clipboard_recent_content =
+          ClipboardRecentContent::GetInstance();
+      clipboard_recent_content->GetRecentImageFromClipboard(base::BindOnce(
+          &OmniboxViewIOS::OnReceiveClipboardImageForOpenMatch,
+          weak_ptr_factory_.GetWeakPtr(), match, disposition, alternate_nav_url,
+          pasted_text, index, match_selection_timestamp));
+      return;
+    }
+  }
+  model()->OpenSelection(OmniboxPopupSelection(index),
+                         match_selection_timestamp, disposition);
 }
diff --git a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
index 0274806f..ead698b 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/password/password_details/BUILD.gn
@@ -118,6 +118,7 @@
   testonly = true
   sources = [
     "add_password_view_controller_unittest.mm",
+    "password_details_coordinator_unittest.mm",
     "password_details_table_view_controller_unittest.mm",
   ]
   deps = [
@@ -131,6 +132,7 @@
     "//components/strings",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/browser_state:test_support",
+    "//ios/chrome/browser/credential_provider_promo:features",
     "//ios/chrome/browser/main:test_support",
     "//ios/chrome/browser/passwords",
     "//ios/chrome/browser/shared/public/commands",
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
index 01e2061..4b2634e7 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator.mm
@@ -49,9 +49,6 @@
   password_manager::AffiliatedGroup _affiliatedGroup;
   password_manager::CredentialUIEntry _credential;
 
-  // The handler used for CredentialProviderPromoCommands.
-  id<CredentialProviderPromoCommands> _credentialProviderPromoHandler;
-
   // Tells whether or not to support move to account option. If YES, move option
   // will be supported, NO otherwise.
   BOOL _supportMoveToAccount;
@@ -97,10 +94,6 @@
     _credential = credential;
     _reauthenticationModule = reauthModule;
     _supportMoveToAccount = supportMoveToAccount;
-    if (IsCredentialProviderExtensionPromoEnabledOnPasswordCopied()) {
-      _credentialProviderPromoHandler = HandlerForProtocol(
-          browser->GetCommandDispatcher(), CredentialProviderPromoCommands);
-    }
   }
   return self;
 }
@@ -122,10 +115,6 @@
     _affiliatedGroup = affiliatedGroup;
     _reauthenticationModule = reauthModule;
     _supportMoveToAccount = supportMoveToAccount;
-    if (IsCredentialProviderExtensionPromoEnabledOnPasswordCopied()) {
-      _credentialProviderPromoHandler = HandlerForProtocol(
-          browser->GetCommandDispatcher(), CredentialProviderPromoCommands);
-    }
   }
   return self;
 }
@@ -350,8 +339,10 @@
 
 - (void)onPasswordCopiedByUser {
   if (IsCredentialProviderExtensionPromoEnabledOnPasswordCopied()) {
-    DCHECK(_credentialProviderPromoHandler);
-    [_credentialProviderPromoHandler
+    id<CredentialProviderPromoCommands> credentialProviderPromoHandler =
+        HandlerForProtocol(self.browser->GetCommandDispatcher(),
+                           CredentialProviderPromoCommands);
+    [credentialProviderPromoHandler
         showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
                                                    PasswordCopied];
   }
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_unittest.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_unittest.mm
new file mode 100644
index 0000000..ceaf16c
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_coordinator_unittest.mm
@@ -0,0 +1,103 @@
+// 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/ui/settings/password/password_details/password_details_coordinator.h"
+
+#import "base/test/scoped_feature_list.h"
+#import "base/test/task_environment.h"
+#import "components/password_manager/core/browser/ui/affiliated_group.h"
+#import "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/credential_provider_promo/features.h"
+#import "ios/chrome/browser/main/test_browser.h"
+#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
+#import "ios/chrome/browser/shared/public/commands/credential_provider_promo_commands.h"
+#import "ios/chrome/browser/ui/settings/password/password_details/password_details_handler.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+// Test fixture for testing the PasswordDetailsCoordinatorTest class.
+class PasswordDetailsCoordinatorTest : public PlatformTest {
+ protected:
+  PasswordDetailsCoordinatorTest()
+      : browser_state_(TestChromeBrowserState::Builder().Build()),
+        browser_(std::make_unique<TestBrowser>(browser_state_.get())) {
+    UINavigationController* navigation_controller =
+        [[UINavigationController alloc] init];
+    const password_manager::AffiliatedGroup& affiliateGroup =
+        password_manager::AffiliatedGroup();
+    coordinator_ = [[PasswordDetailsCoordinator alloc]
+        initWithBaseNavigationController:navigation_controller
+                                 browser:browser_.get()
+                         affiliatedGroup:affiliateGroup
+                            reauthModule:nil
+                    supportMoveToAccount:YES];
+  }
+
+  web::WebTaskEnvironment task_environment_;
+  std::unique_ptr<TestChromeBrowserState> browser_state_;
+  std::unique_ptr<Browser> browser_;
+
+  PasswordDetailsCoordinator* coordinator_;
+};
+
+#pragma mark - Tests
+
+// Tests that OnPasswordCopied will dispatch `CredentialProviderPromoCommands`.
+TEST_F(PasswordDetailsCoordinatorTest, OnPasswordCopiedTest) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeatureWithParameters(
+      kCredentialProviderExtensionPromo,
+      {{"enable_promo_on_password_copied", "true"}});
+
+  // Register the command handler for `CredentialProviderPromoCommands`
+  id credential_provider_promo_commands_handler_mock =
+      OCMStrictProtocolMock(@protocol(CredentialProviderPromoCommands));
+  [browser_.get()->GetCommandDispatcher()
+      startDispatchingToTarget:credential_provider_promo_commands_handler_mock
+                   forProtocol:@protocol(CredentialProviderPromoCommands)];
+
+  // Expect the call with correct trigger type.
+  [[credential_provider_promo_commands_handler_mock expect]
+      showCredentialProviderPromoWithTrigger:CredentialProviderPromoTrigger::
+                                                 PasswordCopied];
+
+  // Call the tested function.
+  ASSERT_TRUE(
+      [coordinator_ conformsToProtocol:@protocol(PasswordDetailsHandler)]);
+  [(id<PasswordDetailsHandler>)coordinator_ onPasswordCopiedByUser];
+
+  // Verify.
+  [credential_provider_promo_commands_handler_mock verify];
+}
+
+// Tests that OnPasswordCopied will not dispatch
+// `CredentialProviderPromoCommands`.
+TEST_F(PasswordDetailsCoordinatorTest,
+       OnPasswordCopiedTestCredentialProviderPromoDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  // Enable another arm that will not lead to dispatching the
+  // CredentialProviderPromoCommands.
+  feature_list.InitAndEnableFeatureWithParameters(
+      kCredentialProviderExtensionPromo,
+      {{"enable_promo_on_password_saved", "true"}});
+
+  // Register the command handler for `CredentialProviderPromoCommands`
+  // Use `OCMStrictProtocolMock` and do not expect any so that an exception will
+  // be raised when there is any invocation.
+  id credential_provider_promo_commands_handler_mock =
+      OCMStrictProtocolMock(@protocol(CredentialProviderPromoCommands));
+  [browser_.get()->GetCommandDispatcher()
+      startDispatchingToTarget:credential_provider_promo_commands_handler_mock
+                   forProtocol:@protocol(CredentialProviderPromoCommands)];
+
+  // Call the tested function.
+  ASSERT_TRUE(
+      [coordinator_ conformsToProtocol:@protocol(PasswordDetailsHandler)]);
+  [(id<PasswordDetailsHandler>)coordinator_ onPasswordCopiedByUser];
+}
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/BUILD.gn b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/BUILD.gn
index 9cc0111..0935df2 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/BUILD.gn
@@ -13,9 +13,13 @@
   deps = [
     ":constants",
     ":tracking_price_ui",
+    "//components/commerce/core:pref_names",
+    "//components/commerce/core:shopping_service",
+    "//components/prefs",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/application_context",
     "//ios/chrome/browser/browser_state",
+    "//ios/chrome/browser/commerce:shopping_service",
     "//ios/chrome/browser/main:public",
     "//ios/chrome/browser/push_notification:push_notification_browser_state_service",
     "//ios/chrome/browser/push_notification:push_notification_browser_state_service_factory",
@@ -25,6 +29,7 @@
     "//ios/chrome/browser/shared/ui/list_model",
     "//ios/chrome/browser/shared/ui/table_view:utils",
     "//ios/chrome/browser/shared/ui/table_view/cells",
+    "//ios/chrome/browser/signin",
     "//ios/chrome/browser/ui/alert_coordinator",
     "//ui/base",
   ]
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.h b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.h
index 4edbc4f..0754ecf 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.h
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.h
@@ -13,4 +13,7 @@
 // The accessibility identifier of the Tracking Price mobile notifications cell.
 extern NSString* const kSettingsTrackingPriceMobileNotificationsCellId;
 
+// The accessibility identifier of the Tracking Price email notifications cell.
+extern NSString* const kSettingsTrackingPriceEmailNotificationsCellId;
+
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRICE_NOTIFICATIONS_TRACKING_PRICE_TRACKING_PRICE_CONSTANTS_H_
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.mm b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.mm
index 3ea1376..d496fc639 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.mm
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.mm
@@ -12,3 +12,6 @@
 
 NSString* const kSettingsTrackingPriceMobileNotificationsCellId =
     @"kSettingsTrackingPriceMobileNotificationsCellId";
+
+NSString* const kSettingsTrackingPriceEmailNotificationsCellId =
+    @"kSettingsTrackingPriceMobileNotificationsCellId";
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_consumer.h b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_consumer.h
index 9a151e2..7578f48 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_consumer.h
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_consumer.h
@@ -22,6 +22,9 @@
 - (void)setTrackPriceHeaderItem:
     (TableViewHeaderFooterItem*)trackPriceHeaderItem;
 
+// Initializes 'emailNotificationItem'.
+- (void)setEmailNotificationItem:(TableViewItem*)emailNotificationItem;
+
 @end
 
 #endif  // IOS_CHROME_BROWSER_UI_SETTINGS_PRICE_NOTIFICATIONS_TRACKING_PRICE_TRACKING_PRICE_CONSUMER_H_
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_coordinator.mm b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_coordinator.mm
index 9c81ac30..2ed1612 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_coordinator.mm
@@ -7,9 +7,13 @@
 #import "base/check.h"
 #import "base/check_op.h"
 #import "base/mac/foundation_util.h"
+#import "components/commerce/core/shopping_service.h"
 #import "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/commerce/shopping_service_factory.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
+#import "ios/chrome/browser/signin/authentication_service_factory.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller.h"
@@ -50,8 +54,16 @@
   self.viewController = [[TrackingPriceViewController alloc]
       initWithStyle:ChromeTableViewStyle()];
   self.viewController.presentationDelegate = self;
+  commerce::ShoppingService* shoppingService =
+      commerce::ShoppingServiceFactory::GetForBrowserState(
+          self.browser->GetBrowserState());
+  AuthenticationService* authService =
+      AuthenticationServiceFactory::GetForBrowserState(
+          self.browser->GetBrowserState());
   self.mediator = [[TrackingPriceMediator alloc]
-      initWithBrowserState:self.browser->GetBrowserState()];
+      initWithShoppingService:shoppingService
+        authenticationService:authService
+                  prefService:self.browser->GetBrowserState()->GetPrefs()];
   self.mediator.consumer = self.viewController;
   self.mediator.presenter = self;
   self.viewController.modelDelegate = self.mediator;
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.h b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.h
index 831ac5a..6e77b0ba 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.h
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.h
@@ -10,17 +10,24 @@
 #import "base/memory/weak_ptr.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller_delegate.h"
 
+class AuthenticationService;
+class PrefService;
+@class TableViewSwitchItem;
 @protocol TrackingPriceAlertPresenter;
 @protocol TrackingPriceConsumer;
-class ChromeBrowserState;
-@class TableViewSwitchItem;
+
+namespace commerce {
+class ShoppingService;
+}  // namespace commerce
 
 // Mediator for the Tracking Price.
 @interface TrackingPriceMediator
     : NSObject <TrackingPriceViewControllerDelegate>
 
-- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState
-    NS_DESIGNATED_INITIALIZER;
+- (instancetype)
+    initWithShoppingService:(commerce::ShoppingService*)shoppingService
+      authenticationService:(AuthenticationService*)authenticationService
+                prefService:(PrefService*)prefService NS_DESIGNATED_INITIALIZER;
 
 - (instancetype)init NS_UNAVAILABLE;
 
@@ -30,6 +37,9 @@
 // Mobile notification item.
 @property(nonatomic, strong) TableViewSwitchItem* mobileNotificationItem;
 
+// Email notification item.
+@property(nonatomic, strong) TableViewSwitchItem* emailNotificationItem;
+
 // Handler for displaying price tracking related alerts.
 @property(nonatomic, weak) id<TrackingPriceAlertPresenter> presenter;
 
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.mm b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.mm
index 08346542..f7c6836 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.mm
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_mediator.mm
@@ -6,8 +6,12 @@
 
 #import "base/mac/foundation_util.h"
 #import "base/notreached.h"
+#import "base/strings/sys_string_conversions.h"
+#import "components/commerce/core/pref_names.h"
+#import "components/commerce/core/price_tracking_utils.h"
+#import "components/commerce/core/shopping_service.h"
+#import "components/prefs/pref_service.h"
 #import "ios/chrome/browser/application_context/application_context.h"
-#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/push_notification/push_notification_account_context_manager.h"
 #import "ios/chrome/browser/push_notification/push_notification_browser_state_service.h"
@@ -18,6 +22,7 @@
 #import "ios/chrome/browser/shared/ui/list_model/list_model.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_link_header_footer_item.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_switch_item.h"
+#import "ios/chrome/browser/signin/authentication_service.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_alert_presenter.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_constants.h"
 #import "ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_consumer.h"
@@ -32,24 +37,42 @@
 typedef NS_ENUM(NSInteger, ItemType) {
   ItemTypeMobileNotifications = kItemTypeEnumZero,
   ItemTypeTrackPriceHeader,
+  ItemTypeEmailNotifications,
 };
 
-@interface TrackingPriceMediator () {
-  base::WeakPtr<ChromeBrowserState> _browserState;
-}
+@interface TrackingPriceMediator ()
 
 // Header item.
 @property(nonatomic, strong)
     TableViewLinkHeaderFooterItem* trackPriceHeaderItem;
 
+// The service responsible for interacting with commerce's price data
+// infrastructure.
+@property(nonatomic, assign) commerce::ShoppingService* shoppingService;
+
+// Responsible for retrieving the relevant information about the currently
+// signed-in user.
+@property(nonatomic, assign) AuthenticationService* authService;
+
+//
+@property(nonatomic, assign) PrefService* prefService;
+
 @end
 
 @implementation TrackingPriceMediator
 
-- (instancetype)initWithBrowserState:(ChromeBrowserState*)browser_state {
+- (instancetype)
+    initWithShoppingService:(commerce::ShoppingService*)shoppingService
+      authenticationService:(AuthenticationService*)authenticationService
+                prefService:(PrefService*)prefService {
   self = [super init];
   if (self) {
-    _browserState = browser_state->AsWeakPtr();
+    DCHECK(shoppingService);
+    DCHECK(authenticationService);
+    DCHECK(prefService);
+    _shoppingService = shoppingService;
+    _authService = authenticationService;
+    _prefService = prefService;
   }
 
   return self;
@@ -72,6 +95,27 @@
   return _mobileNotificationItem;
 }
 
+- (TableViewSwitchItem*)emailNotificationItem {
+  if (!_emailNotificationItem) {
+    id<SystemIdentity> identity =
+        _authService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
+    _emailNotificationItem =
+        [[TableViewSwitchItem alloc] initWithType:ItemTypeEmailNotifications];
+    _emailNotificationItem.text = l10n_util::GetNSString(
+        IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_TITLE);
+    _emailNotificationItem.detailText = l10n_util::GetNSStringF(
+        IDS_IOS_TRACKING_PRICE_EMAIL_NOTIFICATIONS_DETAILS,
+        base::SysNSStringToUTF16(identity.userEmail));
+    _emailNotificationItem.accessibilityIdentifier =
+        kSettingsTrackingPriceEmailNotificationsCellId;
+    _shoppingService->FetchPriceEmailPref();
+    _emailNotificationItem.on =
+        _prefService->GetBoolean(commerce::kPriceEmailNotificationsEnabled);
+  }
+
+  return _emailNotificationItem;
+}
+
 - (TableViewLinkHeaderFooterItem*)trackPriceHeaderItem {
   if (!_trackPriceHeaderItem) {
     _trackPriceHeaderItem = [[TableViewLinkHeaderFooterItem alloc]
@@ -88,6 +132,7 @@
     return;
   _consumer = consumer;
   [_consumer setMobileNotificationItem:self.mobileNotificationItem];
+  [_consumer setEmailNotificationItem:self.emailNotificationItem];
   [_consumer setTrackPriceHeaderItem:self.trackPriceHeaderItem];
 }
 
@@ -116,6 +161,12 @@
           }];
       break;
     }
+    case ItemTypeEmailNotifications: {
+      _prefService->SetBoolean(commerce::kPriceEmailNotificationsEnabled,
+                               value);
+      self.emailNotificationItem.on = value;
+      break;
+    }
     default:
       // Not a switch.
       NOTREACHED();
@@ -128,19 +179,9 @@
 // Returns whether the push notification enabled feature's, `client_id`,
 // permission status is enabled or disabled for the current user.
 - (BOOL)prefValueForClient:(PushNotificationClientId)clientID {
-  ChromeBrowserState* browser_state = _browserState.get();
-
-  if (!browser_state) {
-    return NO;
-  }
-
-  const PushNotificationAccountContext* account_context =
-      PushNotificationBrowserStateServiceFactory::GetForBrowserState(
-          browser_state)
-          ->GetAccountContext();
-  return
-      [account_context.preferenceMap[@(static_cast<int>(clientID)).stringValue]
-          boolValue];
+  // TODO (crbug.com/1369616): This function is being re-implemented by CL
+  // (crrev.com/c/4133444).
+  return YES;
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller.mm b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller.mm
index ebb2565..e7072c9a 100644
--- a/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/price_notifications/tracking_price/tracking_price_view_controller.mm
@@ -24,6 +24,7 @@
 
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierTrackingPriceContent = kSectionIdentifierEnumZero,
+  SectionIdentifierTrackingPriceEmailNotifications,
 };
 
 }  // namespace
@@ -34,6 +35,8 @@
 @property(nonatomic, strong) TableViewItem* mobileNotificationItem;
 // Tracking price header received by mediator.
 @property(nonatomic, strong) TableViewHeaderFooterItem* trackPriceHeaderItem;
+// Email notification table view item received by mediator.
+@property(nonatomic, strong) TableViewItem* emailNotificationItem;
 
 @end
 
@@ -66,8 +69,12 @@
 
   TableViewModel* model = self.tableViewModel;
   [model addSectionWithIdentifier:SectionIdentifierTrackingPriceContent];
+  [model addSectionWithIdentifier:
+             SectionIdentifierTrackingPriceEmailNotifications];
   [model addItem:self.mobileNotificationItem
       toSectionWithIdentifier:SectionIdentifierTrackingPriceContent];
+  [model addItem:self.emailNotificationItem
+      toSectionWithIdentifier:SectionIdentifierTrackingPriceEmailNotifications];
   [model setHeader:self.trackPriceHeaderItem
       forSectionWithIdentifier:SectionIdentifierTrackingPriceContent];
 }
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
index f6ea428..0ffe5c5 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/grid_view_controller.mm
@@ -402,16 +402,13 @@
     // selection border doesn't show around the selection item. The
     // collection view needs to be updated with the selected item again
     // for it to appear correctly.
-    NSUInteger selectedIndex = self.selectedIndex;
-    if (selectedIndex != NSNotFound) {
-      [self.collectionView
-          selectItemAtIndexPath:CreateIndexPath(selectedIndex)
-                       animated:NO
-                 scrollPosition:UICollectionViewScrollPositionNone];
-      [self updateFractionVisibleOfLastItem];
+    [self deselectAllCollectionViewItemsAnimated:NO];
+    [self selectCollectionViewItemWithID:self.selectedItemID
+                                animated:NO
+                          scrollPosition:UICollectionViewScrollPositionNone];
+    [self updateFractionVisibleOfLastItem];
     }
     self.searchText = nil;
-  }
 }
 
 - (void)setSearchText:(NSString*)searchText {
@@ -487,13 +484,10 @@
     [self animateEmptyStateIn];
     return;
   }
-  UICollectionViewScrollPosition scrollPosition =
-      (self.currentLayout == self.horizontalLayout)
-          ? UICollectionViewScrollPositionCenteredHorizontally
-          : UICollectionViewScrollPositionTop;
-  [self.collectionView selectItemAtIndexPath:CreateIndexPath(self.selectedIndex)
-                                    animated:NO
-                              scrollPosition:scrollPosition];
+
+  [self deselectAllCollectionViewItemsAnimated:NO];
+  [self selectCollectionViewItemWithID:self.selectedItemID animated:NO];
+
   // Update the delegate, in case it wasn't set when `items` was populated.
   [self.delegate gridViewController:self didChangeItemCount:self.items.count];
   [self removeEmptyStateAnimated:NO];
@@ -1159,10 +1153,12 @@
   self.selectedItemID = selectedItemID;
   [self.selectedEditingItemIDs removeAllObjects];
   [self.selectedSharableEditingItemIDs removeAllObjects];
+
   [self reloadTabs];
-  [self.collectionView selectItemAtIndexPath:CreateIndexPath(self.selectedIndex)
-                                    animated:NO
-                              scrollPosition:UICollectionViewScrollPositionTop];
+
+  [self deselectAllCollectionViewItemsAnimated:NO];
+  [self selectCollectionViewItemWithID:self.selectedItemID animated:NO];
+
   if ([self shouldShowEmptyState]) {
     [self animateEmptyStateIn];
   } else {
@@ -1204,20 +1200,11 @@
     [self removeEmptyStateAnimated:YES];
     [self.collectionView insertItemsAtIndexPaths:@[ CreateIndexPath(index) ]];
   };
-  NSString* previouslySelectedItemID = self.selectedItemID;
+
   auto completion = ^(BOOL finished) {
-    [self.collectionView
-        deselectItemAtIndexPath:CreateIndexPath([self
-                                    indexOfItemWithID:previouslySelectedItemID])
-                       animated:NO];
-    UICollectionViewScrollPosition scrollPosition =
-        (self.currentLayout == self.horizontalLayout)
-            ? UICollectionViewScrollPositionCenteredHorizontally
-            : UICollectionViewScrollPositionNone;
-    [self.collectionView
-        selectItemAtIndexPath:CreateIndexPath(self.selectedIndex)
-                     animated:NO
-               scrollPosition:scrollPosition];
+    [self deselectAllCollectionViewItemsAnimated:NO];
+    [self selectCollectionViewItemWithID:self.selectedItemID animated:NO];
+
     [self.delegate gridViewController:self didChangeItemCount:self.items.count];
 
     // Check `index` boundaries in order to filter out possible race
@@ -1266,10 +1253,10 @@
   __weak __typeof(self) weakSelf = self;
   auto completion = ^(BOOL finished) {
     if (weakSelf.items.count > 0) {
-      [weakSelf.collectionView
-          selectItemAtIndexPath:CreateIndexPath(weakSelf.selectedIndex)
-                       animated:NO
-                 scrollPosition:UICollectionViewScrollPositionNone];
+      [self deselectAllCollectionViewItemsAnimated:NO];
+      [self selectCollectionViewItemWithID:weakSelf.selectedItemID
+                                  animated:NO
+                            scrollPosition:UICollectionViewScrollPositionNone];
     }
     [weakSelf.delegate gridViewController:weakSelf
                        didChangeItemCount:weakSelf.items.count];
@@ -1294,14 +1281,12 @@
   if ([self.selectedItemID isEqualToString:selectedItemID])
     return;
 
-  [self.collectionView
-      deselectItemAtIndexPath:CreateIndexPath(self.selectedIndex)
-                     animated:NO];
+  [self deselectAllCollectionViewItemsAnimated:NO];
+
   self.selectedItemID = selectedItemID;
-  [self.collectionView
-      selectItemAtIndexPath:CreateIndexPath(self.selectedIndex)
-                   animated:NO
-             scrollPosition:UICollectionViewScrollPositionNone];
+  [self selectCollectionViewItemWithID:self.selectedItemID
+                              animated:NO
+                        scrollPosition:UICollectionViewScrollPositionNone];
   [self updateVisibleCellsOpacity];
 }
 
@@ -1364,12 +1349,13 @@
                        [weakSelf.collectionView reloadItemsAtIndexPaths:@[
                          CreateIndexPath(weakSelf.selectedIndex)
                        ]];
-                       [weakSelf.collectionView
-                           selectItemAtIndexPath:CreateIndexPath(
-                                                     weakSelf.selectedIndex)
-                                        animated:NO
-                                  scrollPosition:
-                                      UICollectionViewScrollPositionNone];
+                       [self deselectAllCollectionViewItemsAnimated:NO];
+                       [self
+                           selectCollectionViewItemWithID:weakSelf
+                                                              .selectedItemID
+                                                 animated:NO
+                                           scrollPosition:
+                                               UICollectionViewScrollPositionNone];
                      }
                      completion:nil];
   };
@@ -1540,6 +1526,49 @@
 
 #pragma mark - Private
 
+// Selects the collection view's item with `itemID`.
+- (void)selectCollectionViewItemWithID:(NSString*)itemID
+                              animated:(BOOL)animated
+                        scrollPosition:
+                            (UICollectionViewScrollPosition)scrollPosition {
+  NSUInteger itemIndex = [self indexOfItemWithID:itemID];
+
+  // Check `itemIndex` boundaries in order to filter out possible race
+  // conditions while mutating the collection.
+  if (itemIndex == NSNotFound || itemIndex >= self.items.count) {
+    return;
+  }
+
+  NSIndexPath* itemIndexPath = CreateIndexPath(itemIndex);
+
+  [self.collectionView selectItemAtIndexPath:itemIndexPath
+                                    animated:animated
+                              scrollPosition:scrollPosition];
+}
+
+// Selects the collection view's item with `itemID`.
+- (void)selectCollectionViewItemWithID:(NSString*)itemID
+                              animated:(BOOL)animated {
+  UICollectionViewScrollPosition scrollPosition =
+      (self.currentLayout == self.horizontalLayout)
+          ? UICollectionViewScrollPositionCenteredHorizontally
+          : UICollectionViewScrollPositionTop;
+
+  [self selectCollectionViewItemWithID:itemID
+                              animated:animated
+                        scrollPosition:scrollPosition];
+}
+
+// Deselects all the collection view items.
+- (void)deselectAllCollectionViewItemsAnimated:(BOOL)animated {
+  NSArray<NSIndexPath*>* indexPathsForSelectedItems =
+      [self.collectionView indexPathsForSelectedItems];
+  for (NSIndexPath* itemIndexPath in indexPathsForSelectedItems) {
+    [self.collectionView deselectItemAtIndexPath:itemIndexPath
+                                        animated:animated];
+  }
+}
+
 - (void)voiceOverStatusDidChange {
   self.collectionView.dragInteractionEnabled =
       [self shouldEnableDrapAndDropInteraction];
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_view_controller.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_view_controller.mm
index 6e45fd7..12b7838f 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_view_controller.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/pinned_tabs/pinned_tabs_view_controller.mm
@@ -125,6 +125,7 @@
 - (void)contentWillAppearAnimated:(BOOL)animated {
   [self.collectionView reloadData];
 
+  [self deselectAllCollectionViewItemsAnimated:NO];
   [self selectCollectionViewItemWithID:_selectedItemID animated:NO];
   [self scrollCollectionViewToSelectedItemAnimated:NO];
 
@@ -276,6 +277,8 @@
   [self.delegate pinnedTabsViewController:self didChangeItemCount:items.count];
 
   [self.collectionView reloadData];
+
+  [self deselectAllCollectionViewItemsAnimated:YES];
   [self selectCollectionViewItemWithID:_selectedItemID animated:YES];
   [self scrollCollectionViewToSelectedItemAnimated:YES];
 }
@@ -286,8 +289,6 @@
   // Consistency check: `item`'s ID is not in `_items`.
   DCHECK([self indexOfItemWithID:item.identifier] == NSNotFound);
 
-  NSString* previousItemID = _selectedItemID;
-
   __weak __typeof(self) weakSelf = self;
   [self.collectionView
       performBatchUpdates:^{
@@ -296,8 +297,7 @@
                                       selectedItemID:selectedItemID];
       }
       completion:^(BOOL completed) {
-        [weakSelf
-            handleItemInsertionCompletionWithPreviousItemID:previousItemID];
+        [weakSelf handleItemInsertionCompletion];
       }];
 }
 
@@ -326,7 +326,8 @@
     return;
   }
 
-  [self deselectCollectionViewItemWithID:_selectedItemID animated:NO];
+  [self deselectAllCollectionViewItemsAnimated:NO];
+
   _selectedItemID = selectedItemID;
   [self selectCollectionViewItemWithID:_selectedItemID animated:NO];
   [self scrollCollectionViewToSelectedItemAnimated:NO];
@@ -651,10 +652,8 @@
 }
 
 // Handles the completion of item insertion into the collection view.
-- (void)handleItemInsertionCompletionWithPreviousItemID:
-    (NSString*)previousItemID {
-  [self
-      updateCollectionViewAfterItemInsertionWithPreviousItemID:previousItemID];
+- (void)handleItemInsertionCompletion {
+  [self updateCollectionViewAfterItemInsertion];
   [self.delegate pinnedTabsViewController:self didChangeItemCount:_items.count];
 }
 
@@ -802,11 +801,9 @@
   _dropOverlayView.alpha = visible ? 1 : 0;
 }
 
-// Updates the collection view after an item insertion with the previously
-// selected item id.
-- (void)updateCollectionViewAfterItemInsertionWithPreviousItemID:
-    (NSString*)previousItemID {
-  [self deselectCollectionViewItemWithID:previousItemID animated:NO];
+// Updates the collection view after an item insertion.
+- (void)updateCollectionViewAfterItemInsertion {
+  [self deselectAllCollectionViewItemsAnimated:NO];
   [self selectCollectionViewItemWithID:_selectedItemID animated:NO];
 
   // Scroll the collection view to the newly added item, so it doesn't
@@ -819,6 +816,7 @@
 // Updates the collection view after an item deletion.
 - (void)updateCollectionViewAfterItemDeletion {
   if (_items.count > 0) {
+    [self deselectAllCollectionViewItemsAnimated:NO];
     [self selectCollectionViewItemWithID:_selectedItemID animated:NO];
   } else {
     [self pinnedTabsAvailable:_available];
@@ -841,6 +839,7 @@
                  [self.collectionView reloadItemsAtIndexPaths:@[
                    CreateIndexPath(self.selectedIndex)
                  ]];
+                 [self deselectAllCollectionViewItemsAnimated:NO];
                  [self selectCollectionViewItemWithID:self->_selectedItemID
                                              animated:NO];
                }
@@ -889,13 +888,14 @@
              scrollPosition:UICollectionViewScrollPositionNone];
 }
 
-// Deselects the collection view's item with `itemID`.
-- (void)deselectCollectionViewItemWithID:(NSString*)itemID
-                                animated:(BOOL)animated {
-  NSUInteger itemIndex = [self indexOfItemWithID:itemID];
-  NSIndexPath* itemIndexPath = CreateIndexPath(itemIndex);
-
-  [self.collectionView deselectItemAtIndexPath:itemIndexPath animated:animated];
+// Deselects all the collection view items.
+- (void)deselectAllCollectionViewItemsAnimated:(BOOL)animated {
+  NSArray<NSIndexPath*>* indexPathsForSelectedItems =
+      [self.collectionView indexPathsForSelectedItems];
+  for (NSIndexPath* itemIndexPath in indexPathsForSelectedItems) {
+    [self.collectionView deselectItemAtIndexPath:itemIndexPath
+                                        animated:animated];
+  }
 }
 
 // Scrolls the collection view to the currently selected item.
diff --git a/ios/web/content/BUILD.gn b/ios/web/content/BUILD.gn
index 133def8..b2146bad 100644
--- a/ios/web/content/BUILD.gn
+++ b/ios/web/content/BUILD.gn
@@ -9,6 +9,8 @@
 source_set("content") {
   configs += [ "//build/config/compiler:enable_arc" ]
   sources = [
+    "content_browser_context.h",
+    "content_browser_context.mm",
     "js_messaging/content_web_frames_manager.h",
     "js_messaging/content_web_frames_manager.mm",
     "navigation/content_navigation_context.h",
@@ -25,7 +27,7 @@
   deps = [
     "//base",
     "//build:blink_buildflags",
-    "//content/public/browser:browser",
+    "//content/public/browser",
     "//ios/web/common:user_agent",
     "//ios/web/find_in_page:find_in_page",
     "//ios/web/navigation:core",
diff --git a/ios/web/content/content_browser_context.h b/ios/web/content/content_browser_context.h
new file mode 100644
index 0000000..fefd9ca
--- /dev/null
+++ b/ios/web/content/content_browser_context.h
@@ -0,0 +1,98 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_WEB_CONTENT_CONTENT_BROWSER_CONTEXT_H_
+#define IOS_WEB_CONTENT_CONTENT_BROWSER_CONTEXT_H_
+
+#import "build/blink_buildflags.h"
+#import "content/public/browser/browser_context.h"
+#import "content/public/browser/resource_context.h"
+#import "ios/web/public/browser_state.h"
+
+#if !BUILDFLAG(USE_BLINK)
+#error File can only be included when USE_BLINK is true
+#endif
+
+namespace content {
+class BrowserContext;
+}
+
+namespace web {
+
+class BrowserContextHolder;
+
+// Wraps a web::BrowserState. The lifetime of instances is tied to that
+// of the corresponding ContentBrowserContext (via SharedUserData).
+// This is a temporary solution to unblock other areas of ios/web/content. This
+// class needs to be refactored so BrowserContext and BrowserState can be
+// properly layered on one another.
+class ContentBrowserContext : public content::BrowserContext {
+ public:
+  static content::BrowserContext* FromBrowserState(
+      web::BrowserState* browser_state);
+  explicit ContentBrowserContext(web::BrowserState* browser_state);
+  ~ContentBrowserContext() override;
+
+  // BrowserContext implementation.
+  base::FilePath GetPath() override;
+  std::unique_ptr<content::ZoomLevelDelegate> CreateZoomLevelDelegate(
+      const base::FilePath& partition_path) override;
+  bool IsOffTheRecord() override;
+  content::DownloadManagerDelegate* GetDownloadManagerDelegate() override;
+  content::ResourceContext* GetResourceContext() override;
+  content::BrowserPluginGuestManager* GetGuestManager() override;
+  storage::SpecialStoragePolicy* GetSpecialStoragePolicy() override;
+  content::PlatformNotificationService* GetPlatformNotificationService()
+      override;
+  content::PushMessagingService* GetPushMessagingService() override;
+  content::StorageNotificationService* GetStorageNotificationService() override;
+  content::SSLHostStateDelegate* GetSSLHostStateDelegate() override;
+  content::PermissionControllerDelegate* GetPermissionControllerDelegate()
+      override;
+  content::BackgroundFetchDelegate* GetBackgroundFetchDelegate() override;
+  content::BackgroundSyncController* GetBackgroundSyncController() override;
+  content::BrowsingDataRemoverDelegate* GetBrowsingDataRemoverDelegate()
+      override;
+  content::ContentIndexProvider* GetContentIndexProvider() override;
+  content::ClientHintsControllerDelegate* GetClientHintsControllerDelegate()
+      override;
+  content::FederatedIdentityApiPermissionContextDelegate*
+  GetFederatedIdentityApiPermissionContext() override;
+  content::FederatedIdentityPermissionContextDelegate*
+  GetFederatedIdentityPermissionContext() override;
+  content::ReduceAcceptLanguageControllerDelegate*
+  GetReduceAcceptLanguageControllerDelegate() override;
+  content::OriginTrialsControllerDelegate* GetOriginTrialsControllerDelegate()
+      override;
+
+ private:
+  friend class BrowserContextHolder;
+
+  // Contains URLRequestContextGetter required for resource loading.
+  class ResourceContext : public content::ResourceContext {
+   public:
+    ResourceContext();
+
+    ResourceContext(const ResourceContext&) = delete;
+    ResourceContext& operator=(const ResourceContext&) = delete;
+
+    ~ResourceContext() override;
+  };
+
+  bool ignore_certificate_errors() const { return false; }
+
+  // Remove when refactored to depend on browser_start_->GetStatePath()
+  base::FilePath browser_path_;
+
+  // Performs initialization of the ContentBrowserContext while IO is still
+  // allowed on the current thread.
+  void InitWhileIOAllowed();
+  void FinishInitWhileIOAllowed();
+  std::unique_ptr<ResourceContext> resource_context_;
+  web::BrowserState* browser_state_ = nullptr;
+};
+
+}  // namespace web
+
+#endif  // IOS_WEB_CONTENT_CONTENT_BROWSER_CONTEXT_H_
diff --git a/ios/web/content/content_browser_context.mm b/ios/web/content/content_browser_context.mm
new file mode 100644
index 0000000..0c9ec2b
--- /dev/null
+++ b/ios/web/content/content_browser_context.mm
@@ -0,0 +1,190 @@
+// 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/web/content/content_browser_context.h"
+
+#import <Foundation/Foundation.h>
+
+#import "base/strings/sys_string_conversions.h"
+#import "ios/web/public/thread/web_task_traits.h"
+#import "ios/web/public/thread/web_thread.h"
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+namespace web {
+
+const char kBrowserContextDataKey[] = "browser_context";
+
+class BrowserContextHolder : public base::SupportsUserData::Data {
+ public:
+  static content::BrowserContext* FromBrowserState(
+      web::BrowserState* browser_state) {
+    BrowserContextHolder* holder = static_cast<BrowserContextHolder*>(
+        browser_state->GetUserData(kBrowserContextDataKey));
+    if (!holder) {
+      std::unique_ptr<BrowserContextHolder> data =
+          std::make_unique<BrowserContextHolder>();
+      data->browser_context_ =
+          base::WrapUnique(new ContentBrowserContext(browser_state));
+      browser_state->SetUserData(kBrowserContextDataKey, std::move(data));
+      holder = static_cast<BrowserContextHolder*>(
+          browser_state->GetUserData(kBrowserContextDataKey));
+    }
+    CHECK(holder);
+    return holder->browser_context_.get();
+  }
+
+  BrowserContextHolder() = default;
+
+ private:
+  std::unique_ptr<content::BrowserContext> browser_context_;
+};
+
+content::BrowserContext* ContentBrowserContext::FromBrowserState(
+    web::BrowserState* browser_state) {
+  DCHECK(browser_state);
+  return BrowserContextHolder::FromBrowserState(browser_state);
+}
+
+ContentBrowserContext::ContentBrowserContext(web::BrowserState* browser_state)
+    : resource_context_(std::make_unique<ResourceContext>()),
+      browser_state_(browser_state) {
+  InitWhileIOAllowed();
+
+  // This should depend on browser_state_->GetStatePath(), but it is probably
+  // unsafe to do this right now. Instead, use a temp directory until this is
+  // refactored.
+  browser_path_ =
+      base::FilePath(base::SysNSStringToUTF8(NSTemporaryDirectory()))
+          .Append("Chromium");
+}
+
+ContentBrowserContext::ResourceContext::ResourceContext() {}
+
+ContentBrowserContext::ResourceContext::~ResourceContext() {}
+
+ContentBrowserContext::~ContentBrowserContext() {
+  NotifyWillBeDestroyed();
+
+  // Need to destruct the ResourceContext before posting tasks which may delete
+  // the URLRequestContext because ResourceContext's destructor will remove any
+  // outstanding request while URLRequestContext's destructor ensures that there
+  // are no more outstanding requests.
+  if (resource_context_) {
+    GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
+                                          resource_context_.release());
+  }
+  ShutdownStoragePartitions();
+}
+
+void ContentBrowserContext::InitWhileIOAllowed() {
+  FinishInitWhileIOAllowed();
+}
+
+void ContentBrowserContext::FinishInitWhileIOAllowed() {}
+
+std::unique_ptr<content::ZoomLevelDelegate>
+ContentBrowserContext::CreateZoomLevelDelegate(const base::FilePath&) {
+  return nullptr;
+}
+
+base::FilePath ContentBrowserContext::GetPath() {
+  return browser_path_;
+}
+
+bool ContentBrowserContext::IsOffTheRecord() {
+  return browser_state_->IsOffTheRecord();
+}
+
+content::DownloadManagerDelegate*
+ContentBrowserContext::GetDownloadManagerDelegate() {
+  return nullptr;
+}
+
+content::ResourceContext* ContentBrowserContext::GetResourceContext() {
+  return resource_context_.get();
+}
+
+content::BrowserPluginGuestManager* ContentBrowserContext::GetGuestManager() {
+  return nullptr;
+}
+
+storage::SpecialStoragePolicy*
+ContentBrowserContext::GetSpecialStoragePolicy() {
+  return nullptr;
+}
+
+content::PlatformNotificationService*
+ContentBrowserContext::GetPlatformNotificationService() {
+  return nullptr;
+}
+
+content::PushMessagingService*
+ContentBrowserContext::GetPushMessagingService() {
+  return nullptr;
+}
+
+content::StorageNotificationService*
+ContentBrowserContext::GetStorageNotificationService() {
+  return nullptr;
+}
+
+content::SSLHostStateDelegate*
+ContentBrowserContext::GetSSLHostStateDelegate() {
+  return nullptr;
+}
+
+content::PermissionControllerDelegate*
+ContentBrowserContext::GetPermissionControllerDelegate() {
+  return nullptr;
+}
+
+content::ClientHintsControllerDelegate*
+ContentBrowserContext::GetClientHintsControllerDelegate() {
+  return nullptr;
+}
+
+content::BackgroundFetchDelegate*
+ContentBrowserContext::GetBackgroundFetchDelegate() {
+  return nullptr;
+}
+
+content::BackgroundSyncController*
+ContentBrowserContext::GetBackgroundSyncController() {
+  return nullptr;
+}
+
+content::BrowsingDataRemoverDelegate*
+ContentBrowserContext::GetBrowsingDataRemoverDelegate() {
+  return nullptr;
+}
+
+content::ContentIndexProvider*
+ContentBrowserContext::GetContentIndexProvider() {
+  return nullptr;
+}
+
+content::FederatedIdentityApiPermissionContextDelegate*
+ContentBrowserContext::GetFederatedIdentityApiPermissionContext() {
+  return nullptr;
+}
+
+content::FederatedIdentityPermissionContextDelegate*
+ContentBrowserContext::GetFederatedIdentityPermissionContext() {
+  return nullptr;
+}
+
+content::ReduceAcceptLanguageControllerDelegate*
+ContentBrowserContext::GetReduceAcceptLanguageControllerDelegate() {
+  return nullptr;
+}
+
+content::OriginTrialsControllerDelegate*
+ContentBrowserContext::GetOriginTrialsControllerDelegate() {
+  return nullptr;
+}
+
+}  // namespace web
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index 6173b83..1583582 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -425,15 +425,6 @@
 #endif
              );
 
-// If non-zero, audio processing is done on a dedicated processing thread which
-// receives audio from the audio capture thread via a fifo of a specified size.
-// Zero fifo size means the usage of such processing thread is disabled and
-// processing is done on the audio capture thread itself.
-const base::FeatureParam<int> kChromeWideEchoCancellationProcessingFifoSize{
-    &kChromeWideEchoCancellation, "processing_fifo_size",
-    110  // Default value for the enabled feature.
-};
-
 // When audio processing is done in the audio process, at the renderer side IPC
 // is set up to receive audio at the processing sample rate. This is a
 // kill-switch to fallback to receiving audio at the default sample rate of the
@@ -457,6 +448,27 @@
 // https://crbug.com/1332484.
 const base::FeatureParam<bool> kChromeWideEchoCancellationAllowAllSampleRates{
     &kChromeWideEchoCancellation, "allow_all_sample_rates", true};
+
+// https://crbug.com/1420568
+// Applicable only if kChromeWideEchoCancellation is enabled.
+// If disabled, the ProcessingAudioFifo size defaults to 110.
+// If enabled, the ProcessingAudioFifo size is set to the value of the fifo_size
+// parameter.
+//
+// If the ProcessingAudioFifo size is non-zero, audio processing is done on a
+// dedicated processing thread which receives audio from the audio capture
+// thread via a fifo of a specified size.
+// If the ProcessingAudioFifo size is zero, the usage of this processing thread
+// is disabled and processing is done on the audio capture thread itself.
+BASE_FEATURE(kDecreaseProcessingAudioFifoSize,
+             "DecreaseProcessingAudioFifoSize",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+const base::FeatureParam<int> kDecreaseProcessingAudioFifoSizeValue{
+    &kDecreaseProcessingAudioFifoSize, "fifo_size",
+    110  // Default value for the enabled feature.
+};
+
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -1424,6 +1436,20 @@
 #endif
 }
 
+int GetProcessingAudioFifoSize() {
+#if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
+  if (!IsChromeWideEchoCancellationEnabled()) {
+    return 0;
+  }
+  if (base::FeatureList::IsEnabled(media::kDecreaseProcessingAudioFifoSize)) {
+    return media::kDecreaseProcessingAudioFifoSizeValue.Get();
+  }
+  return 110;
+#else
+  return 0;
+#endif
+}
+
 bool IsHardwareSecureDecryptionEnabled() {
   return base::FeatureList::IsEnabled(kHardwareSecureDecryption) ||
          base::FeatureList::IsEnabled(kHardwareSecureDecryptionExperiment);
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index b482800..0480182 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -164,14 +164,15 @@
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kCdmProcessSiteIsolation);
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kChromeWideEchoCancellation);
-MEDIA_EXPORT extern const base::FeatureParam<int>
-    kChromeWideEchoCancellationProcessingFifoSize;
 MEDIA_EXPORT extern const base::FeatureParam<bool>
     kChromeWideEchoCancellationMinimizeResampling;
 MEDIA_EXPORT extern const base::FeatureParam<double>
     kChromeWideEchoCancellationDynamicMixingTimeout;
 MEDIA_EXPORT extern const base::FeatureParam<bool>
     kChromeWideEchoCancellationAllowAllSampleRates;
+MEDIA_EXPORT BASE_DECLARE_FEATURE(kDecreaseProcessingAudioFifoSize);
+MEDIA_EXPORT extern const base::FeatureParam<int>
+    kDecreaseProcessingAudioFifoSizeValue;
 #endif
 #if BUILDFLAG(IS_CHROMEOS)
 MEDIA_EXPORT BASE_DECLARE_FEATURE(kCrOSSystemAEC);
@@ -421,6 +422,7 @@
     const base::CommandLine& command_line);
 
 MEDIA_EXPORT bool IsChromeWideEchoCancellationEnabled();
+MEDIA_EXPORT int GetProcessingAudioFifoSize();
 MEDIA_EXPORT bool IsHardwareSecureDecryptionEnabled();
 MEDIA_EXPORT bool IsVideoCaptureAcceleratedJpegDecodingEnabled();
 
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index 57d576c..3531255 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -404,10 +404,7 @@
   }
 
   if (enable_hls_demuxer) {
-    sources += [
-      "hls_data_source_provider_unittest.cc",
-      "hls_demuxer_unittest.cc",
-    ]
+    sources += [ "hls_data_source_provider_unittest.cc" ]
   }
 
   if (media_use_ffmpeg) {
diff --git a/media/formats/hls/rendition_selector.cc b/media/formats/hls/rendition_selector.cc
index 000c43e..c5c1f2d 100644
--- a/media/formats/hls/rendition_selector.cc
+++ b/media/formats/hls/rendition_selector.cc
@@ -236,27 +236,31 @@
       return result;
     }
     result.selected_variant = acceptable_audio[0];
-
-    // If our primary variant is audio, but we have a preferred rendition, we
-    // should then only provide audio override, and not a default variant.
-    result.audio_override =
+    result.audio_override_rendition =
         TryFindAudioOverride(audio_preferences, result.selected_variant);
 
-    if (result.audio_override != nullptr) {
+    // If our selected variant is audio, but we got a rendition that overrides
+    // this, then this selected variant won't actually be selected, since it
+    // would be overridden by the audio.
+    if (result.audio_override_rendition != nullptr) {
+      result.audio_override_variant = result.selected_variant;
       result.selected_variant = nullptr;
     }
 
     return result;
   }
 
+  // For now, since we only select a potential override from the selected
+  // variant, the audio override variant is always the same.
   result.selected_variant = acceptable_resolutions[0];
+  result.audio_override_variant = acceptable_resolutions[0];
 
   // If our selected variant is an audio/video stream, we should use its audio
   // group to determine what rendition to pick as an override, if it has an
   // audio group. If we don't suspect it of being an audio stream, then we have
   // to decide what to do - most implementations just end up with silent video.
   // Should we consider finding the best audio variant as well?
-  result.audio_override =
+  result.audio_override_rendition =
       TryFindAudioOverride(audio_preferences, result.selected_variant);
   return result;
 }
diff --git a/media/formats/hls/rendition_selector.h b/media/formats/hls/rendition_selector.h
index 1088470..0ac91d3 100644
--- a/media/formats/hls/rendition_selector.h
+++ b/media/formats/hls/rendition_selector.h
@@ -85,7 +85,8 @@
     raw_ptr<const VariantStream> selected_variant;
 
     // Use this variant for audio content if it is not nullptr.
-    raw_ptr<const AudioRendition> audio_override;
+    raw_ptr<const VariantStream> audio_override_variant;
+    raw_ptr<const AudioRendition> audio_override_rendition;
   };
 
   RenditionSelector(scoped_refptr<MultivariantPlaylist> playlist,
diff --git a/media/formats/hls/rendition_selector_unittest.cc b/media/formats/hls/rendition_selector_unittest.cc
index df0068d..22c62619 100644
--- a/media/formats/hls/rendition_selector_unittest.cc
+++ b/media/formats/hls/rendition_selector_unittest.cc
@@ -63,11 +63,12 @@
   }
 
   if (audio_override.has_value()) {
-    ASSERT_NE(variants.audio_override, nullptr);
-    ASSERT_NE(variants.audio_override->GetUri(), absl::nullopt);
-    ASSERT_EQ(*variants.audio_override->GetUri(), GURL(*audio_override));
+    ASSERT_NE(variants.audio_override_rendition, nullptr);
+    ASSERT_NE(variants.audio_override_rendition->GetUri(), absl::nullopt);
+    ASSERT_EQ(*variants.audio_override_rendition->GetUri(),
+              GURL(*audio_override));
   } else {
-    ASSERT_EQ(variants.audio_override, nullptr);
+    ASSERT_EQ(variants.audio_override_rendition, nullptr);
   }
 }
 
@@ -133,8 +134,7 @@
 
   // There are no variants that we've detected as having video, so we end up
   // with the fallback to selecting from a bunch of audio-only variants. There
-  // aren't any renditions either, so there shouldn't be an audio-override
-  // rendition selected either.
+  // aren't any renditions either, so this will count as a primary variant.
   CheckSelections(rs,
                   /*video_prefs=*/{absl::nullopt, absl::nullopt},
                   /*audio_prefs=*/{absl::nullopt, absl::nullopt},
diff --git a/mojo/core/embedder/embedder.cc b/mojo/core/embedder/embedder.cc
index 868b521e..5e55a0e 100644
--- a/mojo/core/embedder/embedder.cc
+++ b/mojo/core/embedder/embedder.cc
@@ -39,7 +39,7 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
 std::atomic<bool> g_mojo_ipcz_enabled{false};
 #else
 // Default to enabled even if InitFeatures() is never called.
diff --git a/mojo/core/embedder/features.cc b/mojo/core/embedder/features.cc
index c35da94..d8efb6b 100644
--- a/mojo/core/embedder/features.cc
+++ b/mojo/core/embedder/features.cc
@@ -37,7 +37,7 @@
              "MojoAvoidRandomPipeId",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_FUCHSIA)
+#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
 BASE_FEATURE(kMojoIpcz, "MojoIpcz", base::FEATURE_DISABLED_BY_DEFAULT);
 #else
 BASE_FEATURE(kMojoIpcz, "MojoIpcz", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/mojo/core/test/mojo_test_suite_base.cc b/mojo/core/test/mojo_test_suite_base.cc
index 98a5218..4343e77 100644
--- a/mojo/core/test/mojo_test_suite_base.cc
+++ b/mojo/core/test/mojo_test_suite_base.cc
@@ -12,6 +12,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "mojo/core/test/scoped_mojo_support.h"
+#include "mojo/core/test/test_switches.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace mojo::core::test {
@@ -108,7 +109,9 @@
   child_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
   child_feature_list_->InitFromCommandLine(enabled, disabled);
 
-  child_mojo_support_ = std::make_unique<ScopedMojoSupport>();
+  if (!command_line.HasSwitch(test_switches::kNoMojo)) {
+    child_mojo_support_ = std::make_unique<ScopedMojoSupport>();
+  }
 }
 
 }  // namespace mojo::core::test
diff --git a/mojo/core/test/run_all_perftests.cc b/mojo/core/test/run_all_perftests.cc
index 694533a8..16b58e4a 100644
--- a/mojo/core/test/run_all_perftests.cc
+++ b/mojo/core/test/run_all_perftests.cc
@@ -2,25 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/base_switches.h"
+#include "base/check.h"
 #include "base/command_line.h"
-#include "base/test/multiprocess_test.h"
+#include "base/feature_list.h"
 #include "base/test/perf_test_suite.h"
-#include "base/test/test_io_thread.h"
-#include "mojo/core/embedder/embedder.h"
-#include "mojo/core/embedder/scoped_ipc_support.h"
-#include "mojo/core/test/multiprocess_test_helper.h"
+#include "mojo/core/test/scoped_mojo_support.h"
 #include "mojo/core/test/test_support_impl.h"
 #include "mojo/public/tests/test_support_private.h"
 
 int main(int argc, char** argv) {
+  CHECK(base::CommandLine::Init(argc, argv));
+  const auto& cmd = *base::CommandLine::ForCurrentProcess();
+  auto features = std::make_unique<base::FeatureList>();
+  features->InitializeFromCommandLine(
+      cmd.GetSwitchValueASCII(switches::kEnableFeatures),
+      cmd.GetSwitchValueASCII(switches::kDisableFeatures));
+  base::FeatureList::SetInstance(std::move(features));
+
   base::PerfTestSuite test(argc, argv);
-
-  mojo::core::Init();
-  base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart);
-  mojo::core::ScopedIPCSupport ipc_support(
-      test_io_thread.task_runner(),
-      mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN);
+  mojo::core::test::ScopedMojoSupport mojo_support;
   mojo::test::TestSupport::Init(new mojo::core::test::TestSupportImpl());
-
   return test.Run();
 }
diff --git a/mojo/core/test/test_switches.cc b/mojo/core/test/test_switches.cc
index 9323664..b2d81ec 100644
--- a/mojo/core/test/test_switches.cc
+++ b/mojo/core/test/test_switches.cc
@@ -10,4 +10,8 @@
 // honored for test runners using MojoTestSuiteBase.
 const char kMojoIsBroker[] = "mojo-is-broker";
 
+// Disables Mojo initialization completely in the process. Only applies to
+// test child processes. See base::MultiprocessTest.
+const char kNoMojo[] = "no-mojo";
+
 }  // namespace test_switches
diff --git a/mojo/core/test/test_switches.h b/mojo/core/test/test_switches.h
index 80ea422..5092a3d 100644
--- a/mojo/core/test/test_switches.h
+++ b/mojo/core/test/test_switches.h
@@ -8,6 +8,7 @@
 namespace test_switches {
 
 extern const char kMojoIsBroker[];
+extern const char kNoMojo[];
 
 }  // namespace test_switches
 
diff --git a/sandbox/mac/BUILD.gn b/sandbox/mac/BUILD.gn
index b078486..5e9fc183 100644
--- a/sandbox/mac/BUILD.gn
+++ b/sandbox/mac/BUILD.gn
@@ -72,6 +72,8 @@
   sources = [
     "mojom/mojom_traits_unittest.cc",
     "sandbox_compiler_unittest.cc",
+    "sandbox_test.cc",
+    "sandbox_test.h",
     "seatbelt_exec_unittest.cc",
     "seatbelt_extension_unittest.cc",
     "seatbelt_unittest.cc",
@@ -86,6 +88,7 @@
     "//base/test:test_config",
     "//base/test:test_support",
     "//mojo/core/test:run_all_unittests",
+    "//mojo/core/test:test_support",
     "//testing/gtest",
   ]
 }
diff --git a/sandbox/mac/DEPS b/sandbox/mac/DEPS
index 9e21e470..d54696d 100644
--- a/sandbox/mac/DEPS
+++ b/sandbox/mac/DEPS
@@ -13,4 +13,9 @@
   "sandbox_logging\.(h|cc)": _sandbox_mac_non_base,
   "seatbelt\.(h|cc)": _sandbox_mac_non_base,
   "seatbelt_exec\.(h|cc)": _sandbox_mac_non_base,
+
+  # Test only dependencies.
+  "sandbox_test\.cc": [
+    "+mojo/core",
+  ],
 }
diff --git a/sandbox/mac/sandbox_compiler_unittest.cc b/sandbox/mac/sandbox_compiler_unittest.cc
index dfce6aa..f02d51a 100644
--- a/sandbox/mac/sandbox_compiler_unittest.cc
+++ b/sandbox/mac/sandbox_compiler_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_timeouts.h"
 #include "sandbox/mac/sandbox_compiler.h"
+#include "sandbox/mac/sandbox_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/multiprocess_func_list.h"
 
@@ -32,7 +33,7 @@
 }  // namespace
 
 class SandboxCompilerTest
-    : public base::MultiProcessTest,
+    : public SandboxTest,
       public testing::WithParamInterface<SandboxCompiler::Target> {
  protected:
   base::CommandLine MakeCmdLine(const std::string& procname) override {
diff --git a/sandbox/mac/sandbox_test.cc b/sandbox/mac/sandbox_test.cc
new file mode 100644
index 0000000..4df40641
--- /dev/null
+++ b/sandbox/mac/sandbox_test.cc
@@ -0,0 +1,40 @@
+// 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 "sandbox/mac/sandbox_test.h"
+
+#include "base/base_switches.h"
+#include "base/process/launch.h"
+#include "mojo/core/test/test_switches.h"
+
+namespace sandbox {
+
+SandboxTest::SandboxTest() = default;
+
+SandboxTest::~SandboxTest() = default;
+
+base::Process SandboxTest::SpawnChild(
+    const std::string& procname,
+    CommandLineModifier command_line_modifier) {
+  return SpawnChildWithOptions(procname, base::LaunchOptions{},
+                               std::move(command_line_modifier));
+}
+
+base::Process SandboxTest::SpawnChildWithOptions(
+    const std::string& procname,
+    const base::LaunchOptions& options,
+    CommandLineModifier command_line_modifier) {
+  base::CommandLine command_line(MakeCmdLine(procname));
+
+  // NOTE: Mojo initialization fails inside the test sandbox configuration
+  // due to an internal Abseil dependency on sysctl(). We don't use Mojo in
+  // these test child processes, so suppress its initialization there.
+  command_line.AppendSwitch(test_switches::kNoMojo);
+  if (command_line_modifier) {
+    command_line_modifier->Run(command_line);
+  }
+  return base::SpawnMultiProcessTestChild(procname, command_line, options);
+}
+
+}  // namespace sandbox
diff --git a/sandbox/mac/sandbox_test.h b/sandbox/mac/sandbox_test.h
new file mode 100644
index 0000000..d74b68c2
--- /dev/null
+++ b/sandbox/mac/sandbox_test.h
@@ -0,0 +1,45 @@
+// 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 SANDBOX_MAC_SANDBOX_TEST_H_
+#define SANDBOX_MAC_SANDBOX_TEST_H_
+
+#include <string>
+
+#include "base/command_line.h"
+#include "base/functional/callback.h"
+#include "base/process/process.h"
+#include "base/test/multiprocess_test.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace sandbox {
+
+// Base class for multiprocess sandbox tests. This exists to override some
+// command line preparation behavior for spawned child processes.
+class SandboxTest : public base::MultiProcessTest {
+ public:
+  using CommandLineModifier =
+      absl::optional<base::RepeatingCallback<void(base::CommandLine&)>>;
+
+  SandboxTest();
+  ~SandboxTest() override;
+
+  // Launches a new test child process to run `procname`. If
+  // `command_line_modifier` is not null, it will be run to modify the child
+  // command line immediately before launch. Returns a handle to the launched
+  // process.
+  base::Process SpawnChildWithOptions(
+      const std::string& procname,
+      const base::LaunchOptions& options,
+      CommandLineModifier command_line_modifier = absl::nullopt);
+
+  // Same as SpawnChildWithOptions, but uses a default LaunchOptions value.
+  base::Process SpawnChild(
+      const std::string& procname,
+      CommandLineModifier command_line_modifier = absl::nullopt);
+};
+
+}  // namespace sandbox
+
+#endif  // SANDBOX_MAC_SANDBOX_TEST_H_
diff --git a/sandbox/mac/seatbelt_exec_unittest.cc b/sandbox/mac/seatbelt_exec_unittest.cc
index d8f502da..9c7b7ec 100644
--- a/sandbox/mac/seatbelt_exec_unittest.cc
+++ b/sandbox/mac/seatbelt_exec_unittest.cc
@@ -7,12 +7,13 @@
 #include "base/process/kill.h"
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_timeouts.h"
+#include "sandbox/mac/sandbox_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/multiprocess_func_list.h"
 
 namespace sandbox {
 
-class SeatbeltExecTest : public base::MultiProcessTest {};
+class SeatbeltExecTest : public SandboxTest {};
 
 MULTIPROCESS_TEST_MAIN(ServerTest) {
   std::string profile =
diff --git a/sandbox/mac/seatbelt_extension_unittest.cc b/sandbox/mac/seatbelt_extension_unittest.cc
index e0923130..3b35e9d 100644
--- a/sandbox/mac/seatbelt_extension_unittest.cc
+++ b/sandbox/mac/seatbelt_extension_unittest.cc
@@ -12,9 +12,11 @@
 #include "base/files/file_util.h"
 #include "base/files/scoped_file.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/test/bind.h"
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_timeouts.h"
 #include "sandbox/mac/sandbox_compiler.h"
+#include "sandbox/mac/sandbox_test.h"
 #include "sandbox/mac/seatbelt_extension_token.h"
 #include "testing/multiprocess_func_list.h"
 
@@ -34,7 +36,7 @@
 const char kSwitchFile[] = "test-file";
 const char kSwitchExtension[] = "test-extension";
 
-class SeatbeltExtensionTest : public base::MultiProcessTest {
+class SeatbeltExtensionTest : public SandboxTest {
  public:
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -45,6 +47,23 @@
 
   base::FilePath file_path() { return file_path_; }
 
+  base::Process SpawnChildForPathWithToken(
+      const std::string& procname,
+      const base::FilePath& path,
+      const SeatbeltExtensionToken& token) {
+    // Ensure any symlinks in the path are canonicalized.
+    const base::FilePath canonicalized_path = base::MakeAbsoluteFilePath(path);
+    const std::string token_value = token.token();
+    CHECK(!canonicalized_path.empty());
+    CHECK(!token_value.empty());
+    return SpawnChild(
+        procname,
+        base::BindLambdaForTesting([&](base::CommandLine& command_line) {
+          command_line.AppendSwitchPath(kSwitchFile, canonicalized_path);
+          command_line.AppendSwitchASCII(kSwitchExtension, token_value);
+        }));
+  }
+
  private:
   base::ScopedTempDir temp_dir_;
   base::FilePath file_path_;
@@ -58,16 +77,8 @@
       sandbox::SeatbeltExtension::FILE_READ, file_path().value());
   ASSERT_TRUE(token.get());
 
-  // Ensure any symlinks in the path are canonicalized.
-  base::FilePath path = base::MakeAbsoluteFilePath(file_path());
-  ASSERT_FALSE(path.empty());
-
-  command_line.AppendSwitchPath(kSwitchFile, path);
-  command_line.AppendSwitchASCII(kSwitchExtension, token->token());
-
-  base::Process test_child = base::SpawnMultiProcessTestChild(
-      "FileReadAccess", command_line, base::LaunchOptions());
-
+  base::Process test_child =
+      SpawnChildForPathWithToken("FileReadAccess", file_path(), *token);
   int exit_code = 42;
   test_child.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                     &exit_code);
@@ -147,16 +158,8 @@
       file_path().DirName().value());
   ASSERT_TRUE(token.get());
 
-  // Ensure any symlinks in the path are canonicalized.
-  base::FilePath path = base::MakeAbsoluteFilePath(file_path());
-  ASSERT_FALSE(path.empty());
-
-  command_line.AppendSwitchPath(kSwitchFile, path);
-  command_line.AppendSwitchASCII(kSwitchExtension, token->token());
-
-  base::Process test_child = base::SpawnMultiProcessTestChild(
-      "DirReadWriteAccess", command_line, base::LaunchOptions());
-
+  base::Process test_child =
+      SpawnChildForPathWithToken("DirReadWriteAccess", file_path(), *token);
   int exit_code = 42;
   test_child.WaitForExitWithTimeout(TestTimeouts::action_max_timeout(),
                                     &exit_code);
diff --git a/sandbox/mac/seatbelt_unittest.cc b/sandbox/mac/seatbelt_unittest.cc
index cbf8841..a048c57 100644
--- a/sandbox/mac/seatbelt_unittest.cc
+++ b/sandbox/mac/seatbelt_unittest.cc
@@ -19,13 +19,14 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/test/multiprocess_test.h"
 #include "base/test/test_timeouts.h"
+#include "sandbox/mac/sandbox_test.h"
 #include "sandbox/mac/seatbelt.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/multiprocess_func_list.h"
 
 namespace sandbox {
 
-using SeatbeltTest = base::MultiProcessTest;
+using SeatbeltTest = SandboxTest;
 
 MULTIPROCESS_TEST_MAIN(SandboxCheckTestProcess) {
   CHECK(!Seatbelt::IsSandboxed());
diff --git a/services/audio/input_controller.cc b/services/audio/input_controller.cc
index 3b9ccd3..8238fe6 100644
--- a/services/audio/input_controller.cc
+++ b/services/audio/input_controller.cc
@@ -264,10 +264,7 @@
     return;
   }
 
-  int fifo_size =
-      media::IsChromeWideEchoCancellationEnabled()
-          ? media::kChromeWideEchoCancellationProcessingFifoSize.Get()
-          : 0;
+  int fifo_size = media::GetProcessingAudioFifoSize();
 
   // Only use the FIFO/new thread if its size is explicitly set.
   if (fifo_size) {
diff --git a/services/audio/input_controller_unittest.cc b/services/audio/input_controller_unittest.cc
index 73f5ed47..a75a371c 100644
--- a/services/audio/input_controller_unittest.cc
+++ b/services/audio/input_controller_unittest.cc
@@ -53,17 +53,15 @@
 // more than that for the callbacks.
 constexpr base::TimeDelta kOnMutePollInterval = base::Milliseconds(1000);
 
-enum class ProcessingFifoSetting {
-  kEnabled,
-  kEnabledWithSizeZero,
+enum class ChromeWideEchoCancellationSetting { kEnabled, kDisabled };
+
+enum class DecreaseFifoSizeSetting {
   kDisabled,
+  kDecreasedTo10,
+  kDecreasedTo0,
 };
 
-const std::string kFifoSizeParameter = "processing_fifo_size";
-const std::map<std::string, std::string> kDisabledFifoParam{
-    {kFifoSizeParameter, "0"}};
-const std::map<std::string, std::string> kEnabledFifoParam{
-    {kFifoSizeParameter, "110"}};
+const std::string kFifoSizeParameter = "fifo_size";
 
 }  // namespace
 
@@ -133,7 +131,9 @@
               base::test::TaskEnvironment::TimeSource::MOCK_TIME,
           AudioManagerType audio_manager_type = AudioManagerType::FAKE>
 class TimeSourceInputControllerTest
-    : public ::testing::TestWithParam<ProcessingFifoSetting> {
+    : public ::testing::TestWithParam<
+          std::tuple<ChromeWideEchoCancellationSetting,
+                     DecreaseFifoSizeSetting>> {
  public:
   TimeSourceInputControllerTest()
       : task_environment_(TimeSource),
@@ -152,20 +152,35 @@
                 kSampleRate,
                 kSamplesPerPacket) {
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
-    switch (GetParam()) {
-      case ProcessingFifoSetting::kEnabled:
-        processing_fifo_feature_.InitAndEnableFeatureWithParameters(
-            media::kChromeWideEchoCancellation, kEnabledFifoParam);
+    std::vector<base::test::FeatureRefAndParams> enabled_features;
+    std::vector<base::test::FeatureRef> disabled_features;
+
+    if (GetChromeWideEchoCancellationSetting() ==
+        ChromeWideEchoCancellationSetting::kEnabled) {
+      enabled_features.emplace_back(media::kChromeWideEchoCancellation,
+                                    base::FieldTrialParams());
+    } else {
+      disabled_features.emplace_back(media::kChromeWideEchoCancellation);
+    }
+
+    switch (GetDecreaseFifoSizeSetting()) {
+      case DecreaseFifoSizeSetting::kDisabled:
+        disabled_features.emplace_back(media::kDecreaseProcessingAudioFifoSize);
         break;
-      case ProcessingFifoSetting::kEnabledWithSizeZero:
-        processing_fifo_feature_.InitAndEnableFeatureWithParameters(
-            media::kChromeWideEchoCancellation, kDisabledFifoParam);
+      case DecreaseFifoSizeSetting::kDecreasedTo10:
+        enabled_features.emplace_back(
+            media::kDecreaseProcessingAudioFifoSize,
+            base::FieldTrialParams{{kFifoSizeParameter, "10"}});
         break;
-      case ProcessingFifoSetting::kDisabled:
-        processing_fifo_feature_.InitAndDisableFeature(
-            media::kChromeWideEchoCancellation);
+      case DecreaseFifoSizeSetting::kDecreasedTo0:
+        enabled_features.emplace_back(
+            media::kDecreaseProcessingAudioFifoSize,
+            base::FieldTrialParams{{kFifoSizeParameter, "0"}});
         break;
-    };
+    }
+
+    processing_fifo_feature_.InitWithFeaturesAndParameters(enabled_features,
+                                                           disabled_features);
 #endif
   }
 
@@ -189,7 +204,18 @@
   }
 
   bool IsProcessingFifoEnabled() {
-    return GetParam() == ProcessingFifoSetting::kEnabled;
+    return GetChromeWideEchoCancellationSetting() ==
+               ChromeWideEchoCancellationSetting::kEnabled &&
+           GetDecreaseFifoSizeSetting() !=
+               DecreaseFifoSizeSetting::kDecreasedTo0;
+  }
+
+  ChromeWideEchoCancellationSetting GetChromeWideEchoCancellationSetting() {
+    return std::get<0>(GetParam());
+  }
+
+  DecreaseFifoSizeSetting GetDecreaseFifoSizeSetting() {
+    return std::get<1>(GetParam());
   }
 
   base::test::TaskEnvironment task_environment_;
@@ -211,13 +237,17 @@
 auto test_name_generator =
     [](const ::testing::TestParamInfo<
         TimeSourceInputControllerTest<>::ParamType>& info) {
-      switch (info.param) {
-        case ProcessingFifoSetting::kEnabled:
-          return "FifoEnabled";
-        case ProcessingFifoSetting::kEnabledWithSizeZero:
-          return "FifoEnabledWithSizeZero";
-        case ProcessingFifoSetting::kDisabled:
-          return "FifoDisabled";
+      std::string name_suffix =
+          std::get<0>(info.param) == ChromeWideEchoCancellationSetting::kEnabled
+              ? "CWAECEnabled_"
+              : "CWAECDisabled_";
+      switch (std::get<1>(info.param)) {
+        case DecreaseFifoSizeSetting::kDisabled:
+          return name_suffix + "DecreaseFifoDisabled";
+        case DecreaseFifoSizeSetting::kDecreasedTo10:
+          return name_suffix + "DecreaseFifoTo10";
+        case DecreaseFifoSizeSetting::kDecreasedTo0:
+          return name_suffix + "DecreaseFifoTo0";
       }
     };
 
@@ -371,29 +401,35 @@
   controller_->Close();
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    InputControllerTest,
-    InputControllerTest,
-    testing::Values(ProcessingFifoSetting::kEnabled,
-                    ProcessingFifoSetting::kEnabledWithSizeZero,
-                    ProcessingFifoSetting::kDisabled),
-    test_name_generator);
+auto test_values =
+    testing::ValuesIn(std::vector<TimeSourceInputControllerTest<>::ParamType>{
+        {ChromeWideEchoCancellationSetting::kEnabled,
+         DecreaseFifoSizeSetting::kDisabled},
+        {ChromeWideEchoCancellationSetting::kEnabled,
+         DecreaseFifoSizeSetting::kDecreasedTo10},
+        {ChromeWideEchoCancellationSetting::kEnabled,
+         DecreaseFifoSizeSetting::kDecreasedTo0},
+        {ChromeWideEchoCancellationSetting::kDisabled,
+         DecreaseFifoSizeSetting::kDisabled},
+        {ChromeWideEchoCancellationSetting::kDisabled,
+         DecreaseFifoSizeSetting::kDecreasedTo10},
+        {ChromeWideEchoCancellationSetting::kDisabled,
+         DecreaseFifoSizeSetting::kDecreasedTo0}});
 
-INSTANTIATE_TEST_SUITE_P(
-    SystemTimeInputControllerTest,
-    SystemTimeInputControllerTest,
-    testing::Values(ProcessingFifoSetting::kEnabled,
-                    ProcessingFifoSetting::kEnabledWithSizeZero,
-                    ProcessingFifoSetting::kDisabled),
-    test_name_generator);
+INSTANTIATE_TEST_SUITE_P(InputControllerTest,
+                         InputControllerTest,
+                         test_values,
+                         test_name_generator);
 
-INSTANTIATE_TEST_SUITE_P(
-    InputControllerTestWithMockAudioManager,
-    InputControllerTestWithMockAudioManager,
-    testing::Values(ProcessingFifoSetting::kEnabled,
-                    ProcessingFifoSetting::kEnabledWithSizeZero,
-                    ProcessingFifoSetting::kDisabled),
-    test_name_generator);
+INSTANTIATE_TEST_SUITE_P(SystemTimeInputControllerTest,
+                         SystemTimeInputControllerTest,
+                         test_values,
+                         test_name_generator);
+
+INSTANTIATE_TEST_SUITE_P(InputControllerTestWithMockAudioManager,
+                         InputControllerTestWithMockAudioManager,
+                         test_values,
+                         test_name_generator);
 
 #if BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
 class InputControllerTestHelper {
@@ -413,6 +449,11 @@
         std::move(on_processed_callback));
   }
 
+  int FifoSize() {
+    CHECK(IsUsingProcessingThread());
+    return controller_->processing_fifo_->fifo_size();
+  }
+
  private:
   raw_ptr<InputController> controller_;
 };
@@ -629,6 +670,46 @@
   EXPECT_FALSE(helper_->IsUsingProcessingThread());
 }
 
+TEST_P(InputControllerTestWithDeviceListener, FifoSize) {
+  const std::string kOutputDeviceId = "0x123";
+  EXPECT_CALL(device_output_listener_, StartListening(_, kOutputDeviceId))
+      .Times(1);
+  EXPECT_CALL(device_output_listener_, StopListening(_)).Times(1);
+
+  SetupProcessingConfig(AudioProcessingType::kWithPlayoutReference);
+  CreateAudioController();
+
+  ASSERT_TRUE(controller_.get());
+
+  controller_->SetOutputDeviceForAec(kOutputDeviceId);
+  controller_->Record();
+
+  if (GetChromeWideEchoCancellationSetting() ==
+      ChromeWideEchoCancellationSetting::kDisabled) {
+    EXPECT_FALSE(helper_->IsUsingProcessingThread());
+  } else {
+    switch (GetDecreaseFifoSizeSetting()) {
+      case DecreaseFifoSizeSetting::kDisabled:
+        EXPECT_TRUE(helper_->IsUsingProcessingThread());
+        EXPECT_EQ(helper_->FifoSize(), 110);
+        break;
+      case DecreaseFifoSizeSetting::kDecreasedTo10:
+        EXPECT_TRUE(helper_->IsUsingProcessingThread());
+        EXPECT_EQ(helper_->FifoSize(), 10);
+        break;
+      case DecreaseFifoSizeSetting::kDecreasedTo0:
+        EXPECT_FALSE(helper_->IsUsingProcessingThread());
+        break;
+    }
+  }
+
+  // InputController should offload processing to its own thread, if enabled.
+  EXPECT_EQ(IsProcessingFifoEnabled(), helper_->IsUsingProcessingThread());
+
+  controller_->Close();
+  EXPECT_FALSE(helper_->IsUsingProcessingThread());
+}
+
 TEST_P(InputControllerTestWithDeviceListener, ChangeOutputForAec) {
   const std::string kOutputDeviceId = "0x123";
   const std::string kOtherOutputDeviceId = "0x987";
@@ -714,21 +795,15 @@
   EXPECT_EQ(data_processed_by_fifo, IsProcessingFifoEnabled());
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    InputControllerTestWithDeviceListener,
-    InputControllerTestWithDeviceListener,
-    testing::Values(ProcessingFifoSetting::kEnabled,
-                    ProcessingFifoSetting::kEnabledWithSizeZero,
-                    ProcessingFifoSetting::kDisabled),
-    test_name_generator);
+INSTANTIATE_TEST_SUITE_P(InputControllerTestWithDeviceListener,
+                         InputControllerTestWithDeviceListener,
+                         test_values,
+                         test_name_generator);
 
-INSTANTIATE_TEST_SUITE_P(
-    SystemTimeInputControllerTestWithDeviceListener,
-    SystemTimeInputControllerTestWithDeviceListener,
-    testing::Values(ProcessingFifoSetting::kEnabled,
-                    ProcessingFifoSetting::kEnabledWithSizeZero,
-                    ProcessingFifoSetting::kDisabled),
-    test_name_generator);
+INSTANTIATE_TEST_SUITE_P(SystemTimeInputControllerTestWithDeviceListener,
+                         SystemTimeInputControllerTestWithDeviceListener,
+                         test_values,
+                         test_name_generator);
 
 #endif  // BUILDFLAG(CHROME_WIDE_ECHO_CANCELLATION)
 
diff --git a/services/audio/processing_audio_fifo.h b/services/audio/processing_audio_fifo.h
index c82ec77..b3db72a4 100644
--- a/services/audio/processing_audio_fifo.h
+++ b/services/audio/processing_audio_fifo.h
@@ -74,6 +74,8 @@
   void AttachOnProcessedCallbackForTesting(
       base::RepeatingClosure on_processed_callback);
 
+  int fifo_size() const { return fifo_size_; }
+
  private:
   friend class ProcessingAudioFifoTest;
 
diff --git a/styleguide/c++/checks.md b/styleguide/c++/checks.md
index 7b8da9f8..0a0fb584 100644
--- a/styleguide/c++/checks.md
+++ b/styleguide/c++/checks.md
@@ -15,9 +15,9 @@
 their crash rate. Continuing past an invariant failure can cause crashes and
 incorrect behaviour for our users, but also frequently presents security
 vulnerabilities as attackers may leverage the unexpected state to take control
-of the program. We also reserve the right to let the compiler assume and
-optimize around `DCHECK()`s holding true in non-DCHECK builds using
-`__builtin_assume()`, which even further formalizes undefined behavior.
+of the program. In the future we may let the compiler assume and optimize around
+`DCHECK()`s holding true in non-DCHECK builds using `__builtin_assume()`, which
+further formalizes undefined behavior.
 
 Prefer `CHECK()` and `NOTREACHED_NORETURN()` as they ensure that if an invariant
 fails, the program does not continue in an unexpected state, and we hear about
@@ -47,9 +47,6 @@
 code, while we migrate the preexisting cases to it with care. See
 https://crbug.com/851128.
 
-We reserve the right to make any `DCHECK()` or `NOTREACHED()` statements fatal
-in production builds, subject to performance and code-size constraints.
-
 Below are some examples to explore the choice of `CHECK()` and its variants:
 
 ```c++
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index c83b0cc..3cddac41 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1854,7 +1854,7 @@
       {
         "args": [],
         "cros_board": "dedede",
-        "cros_img": "dedede-release/R113-15390.0.0",
+        "cros_img": "dedede-release/R113-15392.0.0",
         "name": "lacros_all_tast_tests DEDEDE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -1918,7 +1918,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R113-15390.0.0",
+        "cros_img": "eve-release/R113-15392.0.0",
         "name": "lacros_all_tast_tests EVE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2116,7 +2116,7 @@
       {
         "args": [],
         "cros_board": "herobrine",
-        "cros_img": "herobrine-release/R113-15390.0.0",
+        "cros_img": "herobrine-release/R113-15392.0.0",
         "name": "lacros_all_tast_tests HEROBRINE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index 293b7203..6d827b3a 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -1585,7 +1585,8 @@
           "--num-retries=3",
           "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
           "--git-revision=${got_revision}",
-          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw"
+          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter"
         ],
         "check_flakiness_for_new_tests": false,
         "isolate_name": "blink_web_tests",
@@ -1620,44 +1621,6 @@
       },
       {
         "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}",
-          "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_wpt_tests",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "os": "Ubuntu-18.04"
-            }
-          ],
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 10
-        },
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      },
-      {
-        "args": [
           "--test-type=integration"
         ],
         "check_flakiness_for_new_tests": false,
@@ -3780,7 +3743,8 @@
         "args": [
           "--num-retries=3",
           "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}"
+          "--git-revision=${got_revision}",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter"
         ],
         "check_flakiness_for_new_tests": false,
         "isolate_name": "blink_web_tests",
@@ -3817,46 +3781,6 @@
       },
       {
         "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}",
-          "--driver-logging"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_wpt_tests",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Mac-12"
-            }
-          ],
-          "inverse_quickrun_shards": 36,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 18
-        },
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      },
-      {
-        "args": [
           "--test-type=integration"
         ],
         "check_flakiness_for_new_tests": false,
@@ -6057,7 +5981,8 @@
           "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
           "--git-revision=${got_revision}",
           "--target",
-          "Release_x64"
+          "Release_x64",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter"
         ],
         "check_flakiness_for_new_tests": false,
         "isolate_name": "blink_web_tests",
@@ -6094,47 +6019,6 @@
       },
       {
         "args": [
-          "--num-retries=3",
-          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json",
-          "--git-revision=${got_revision}",
-          "--target",
-          "Release_x64"
-        ],
-        "check_flakiness_for_new_tests": false,
-        "isolate_name": "blink_wpt_tests",
-        "isolate_profile_data": true,
-        "merge": {
-          "args": [
-            "--verbose"
-          ],
-          "script": "//third_party/blink/tools/merge_web_test_results.py"
-        },
-        "name": "blink_wpt_tests",
-        "precommit_args": [
-          "--gerrit-issue=${patch_issue}",
-          "--gerrit-patchset=${patch_set}",
-          "--buildbucket-id=${buildbucket_build_id}"
-        ],
-        "resultdb": {
-          "enable": true
-        },
-        "results_handler": "layout tests",
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "cpu": "x86-64",
-              "os": "Windows-10-19045"
-            }
-          ],
-          "inverse_quickrun_shards": 36,
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 18
-        },
-        "test_id_prefix": "ninja://:blink_wpt_tests/"
-      },
-      {
-        "args": [
           "--test-type=integration"
         ],
         "check_flakiness_for_new_tests": false,
diff --git a/testing/buildbot/chromium.dev.json b/testing/buildbot/chromium.dev.json
index 50bc677..f92365903 100644
--- a/testing/buildbot/chromium.dev.json
+++ b/testing/buildbot/chromium.dev.json
@@ -13,7 +13,7 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
-            "base_unittests"
+            "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
@@ -45,8 +45,8 @@
           ],
           "service_account": "chromium-tester-dev@chops-service-accounts.iam.gserviceaccount.com"
         },
-        "test": "base_unittests",
-        "test_id_prefix": "ninja://base:base_unittests/"
+        "test": "chrome_public_smoke_test",
+        "test_id_prefix": "ninja://chrome/android:chrome_public_smoke_test/"
       }
     ]
   },
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 9bc680a5..160180c69 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1164,7 +1164,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R113-15390.0.0",
+        "cros_img": "octopus-release/R113-15392.0.0",
         "name": "lacros_fyi_tast_tests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1212,7 +1212,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R113-15390.0.0",
+        "cros_img": "octopus-release/R113-15392.0.0",
         "name": "ozone_unittests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1263,7 +1263,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R113-15390.0.0",
+        "cros_img": "hana-release/R113-15392.0.0",
         "name": "lacros_all_tast_tests HANA_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1311,7 +1311,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R113-15390.0.0",
+        "cros_img": "strongbad-release/R113-15392.0.0",
         "name": "lacros_all_tast_tests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1359,7 +1359,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R113-15390.0.0",
+        "cros_img": "hana-release/R113-15392.0.0",
         "name": "ozone_unittests HANA_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1403,7 +1403,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R113-15390.0.0",
+        "cros_img": "strongbad-release/R113-15392.0.0",
         "name": "ozone_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1447,7 +1447,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R113-15390.0.0",
+        "cros_img": "hana-release/R113-15392.0.0",
         "name": "viz_unittests HANA_RELEASE_LKGM",
         "swarming": {},
         "test": "viz_unittests",
@@ -1491,7 +1491,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R113-15390.0.0",
+        "cros_img": "strongbad-release/R113-15392.0.0",
         "name": "viz_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "viz_unittests",
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 530af15..d2bffec 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -511,6 +511,7 @@
       'linux-rel-cft': {
         "args": [
           "--additional-env-var=LLVM_PROFILE_FILE=${ISOLATED_OUTDIR}/profraw/default-%2m.profraw",
+	  '--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter',
         ],
         'swarming': {
           'shards': 8,
@@ -527,6 +528,9 @@
         },
       },
       'mac-rel-cft': {
+       "args": [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter',
+        ],
         'swarming': {
           'dimension_sets': [
             {
@@ -578,6 +582,7 @@
         'args': [
           '--target',
           'Release_x64',
+	  '--test-launcher-filter-file=../../testing/buildbot/filters/cft.blink_web_tests.filter',
         ],
         'swarming': {
           "shards": 12,
@@ -615,6 +620,9 @@
       'Win10 Tests x64 (dbg)',
       'devtools_frontend_linux_blink_light_rel',
       'devtools_frontend_linux_blink_light_rel_fastbuild',
+      'linux-rel-cft',
+      'mac-rel-cft',
+      'win-rel-cft',
     ],
     'modifications': {
       'Linux Tests': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index c5e1bf6e..e89e6a6 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -894,6 +894,88 @@
       },
     },
 
+    'chromium_dev_android_gtests': {
+      'chrome_public_smoke_test': {},
+    },
+
+    'chromium_dev_desktop_gtests': {
+      'base_unittests': {},
+      'content_browsertests': {},
+      'content_unittests': {},
+      'interactive_ui_tests': {},
+      'net_unittests': {},
+      'unit_tests': {},
+    },
+
+    'chromium_dev_linux_gtests': {
+      'base_unittests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '8',
+            },
+          ],
+        },
+      },
+      'browser_tests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '8',
+            },
+          ],
+          'shards': 8,
+        },
+      },
+      'content_browsertests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '8',
+            },
+          ],
+          'shards': 5,
+        },
+      },
+      'content_unittests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '2',
+            },
+          ],
+        },
+      },
+      'interactive_ui_tests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '8',
+            },
+          ],
+          'shards': 3,
+        },
+      },
+      'net_unittests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '8',
+            },
+          ],
+        },
+      },
+      'unit_tests': {
+        'swarming': {
+          'dimension_sets': [
+            {
+              'cores': '2',
+            },
+          ],
+        },
+      },
+    },
+
     'chromium_gtests': {
       'absl_hardening_tests': {},
       'angle_unittests': {
@@ -1433,88 +1515,6 @@
       },
     },
 
-    'chromium_swarm_android_gtests': {
-      'base_unittests': {},
-    },
-
-    'chromium_swarm_desktop_gtests': {
-      'base_unittests': {},
-      'content_browsertests': {},
-      'content_unittests': {},
-      'interactive_ui_tests': {},
-      'net_unittests': {},
-      'unit_tests': {},
-    },
-
-    'chromium_swarm_linux_gtests': {
-      'base_unittests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '8',
-            },
-          ],
-        },
-      },
-      'browser_tests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '8',
-            },
-          ],
-          'shards': 8,
-        },
-      },
-      'content_browsertests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '8',
-            },
-          ],
-          'shards': 5,
-        },
-      },
-      'content_unittests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '2',
-            },
-          ],
-        },
-      },
-      'interactive_ui_tests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '8',
-            },
-          ],
-          'shards': 3,
-        },
-      },
-      'net_unittests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '8',
-            },
-          ],
-        },
-      },
-      'unit_tests': {
-        'swarming': {
-          'dimension_sets': [
-            {
-              'cores': '2',
-            },
-          ],
-        },
-      },
-    },
-
     # On some bots we don't have capacity to run all standard tests (for example
     # Android Pie), however there are tracing integration tests we want to
     # ensure are still working.
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index cbf2fba12..1eb44e8 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -456,7 +456,7 @@
     'skylab': {
       'cros_board': 'dedede',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'dedede-release/R113-15390.0.0',
+      'cros_img': 'dedede-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'DEDEDE_RELEASE_LKGM',
@@ -492,7 +492,7 @@
     'skylab': {
       'cros_board': 'eve',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'eve-release/R113-15390.0.0',
+      'cros_img': 'eve-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'EVE_RELEASE_LKGM',
@@ -538,7 +538,7 @@
     'skylab': {
       'cros_board': 'hana',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'hana-release/R113-15390.0.0',
+      'cros_img': 'hana-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'HANA_RELEASE_LKGM',
@@ -574,7 +574,7 @@
     'skylab': {
       'cros_board': 'herobrine',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'herobrine-release/R113-15390.0.0',
+      'cros_img': 'herobrine-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'HEROBRINE_RELEASE_LKGM',
@@ -673,7 +673,7 @@
     'skylab': {
       'cros_board': 'octopus',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'octopus-release/R113-15390.0.0',
+      'cros_img': 'octopus-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_RELEASE_LKGM',
@@ -709,7 +709,7 @@
     'skylab': {
       'cros_board': 'strongbad',
       'cros_chrome_version': '113.0.5650.0',
-      'cros_img': 'strongbad-release/R113-15390.0.0',
+      'cros_img': 'strongbad-release/R113-15392.0.0',
     },
     'enabled': True,
     'identifier': 'STRONGBAD_RELEASE_LKGM',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index a15c19c..22d51a6 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -2584,7 +2584,7 @@
           'walleye',
         ],
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_android_gtests',
+           'gtest_tests': 'chromium_dev_android_gtests',
         },
         'swarming': {
           'dimension_sets': [
@@ -2601,7 +2601,7 @@
           'linux-bionic',
         ],
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_linux_gtests',
+           'gtest_tests': 'chromium_dev_linux_gtests',
         },
       },
       'linux-ssd-rel-swarming': {
@@ -2614,7 +2614,7 @@
           'mac_12_arm64',
         ],
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_desktop_gtests',
+           'gtest_tests': 'chromium_dev_desktop_gtests',
         },
       },
       'mac-rel-swarming': {
@@ -2622,12 +2622,12 @@
           'mac_x64',
         ],
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_desktop_gtests',
+           'gtest_tests': 'chromium_dev_desktop_gtests',
         },
       },
       'win-rel-swarming': {
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_desktop_gtests',
+           'gtest_tests': 'chromium_dev_desktop_gtests',
         },
       },
       'win11-rel-swarming': {
@@ -2635,7 +2635,7 @@
           'win11',
         ],
         'test_suites': {
-           'gtest_tests': 'chromium_swarm_desktop_gtests',
+           'gtest_tests': 'chromium_dev_desktop_gtests',
         },
       },
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 8cd1a7ae..b069c06c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2347,10 +2347,8 @@
                 "android_weblayer",
                 "chromeos",
                 "chromeos_lacros",
-                "fuchsia",
                 "linux",
-                "mac",
-                "windows"
+                "mac"
             ],
             "experiments": [
                 {
@@ -8005,21 +8003,6 @@
             ]
         }
     ],
-    "MojoIpczMac": [
-        {
-            "platforms": [
-                "mac"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "MojoIpcz"
-                    ]
-                }
-            ]
-        }
-    ],
     "MoreAggressiveSolidColorDetection": [
         {
             "platforms": [
@@ -11031,6 +11014,26 @@
             ]
         }
     ],
+    "SearchPrefetchSkipsCancel": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SearchPrefetchSkipsCancel"
+                    ]
+                }
+            ]
+        }
+    ],
     "SearchResumptionModuleAndroid": [
         {
             "platforms": [
@@ -13135,7 +13138,8 @@
                 {
                     "name": "UserLevelMemoryPressureSignalOn4GbDevices",
                     "params": {
-                        "memory_threshold_mb": "169"
+                        "inert_interval_after_loading": "5m",
+                        "memory_threshold_mb": "339"
                     },
                     "enable_features": [
                         "UserLevelMemoryPressureSignalOn4GbDevices"
@@ -13154,7 +13158,8 @@
                 {
                     "name": "UserLevelMemoryPressureSignalOn6GbDevices",
                     "params": {
-                        "memory_threshold_mb": "197"
+                        "inert_interval_after_loading": "5m",
+                        "memory_threshold_mb": "373"
                     },
                     "enable_features": [
                         "UserLevelMemoryPressureSignalOn6GbDevices"
@@ -14744,20 +14749,5 @@
                 }
             ]
         }
-    ],
-    "ZeroCopyTabCaptureStudyWin": [
-        {
-            "platforms": [
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_20230222",
-                    "enable_features": [
-                        "ZeroCopyTabCapture"
-                    ]
-                }
-            ]
-        }
     ]
 }
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 82191b6..dd59d74 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3859,6 +3859,7 @@
   kV8StorageBucketManager_Keys_Method = 4518,
   kV8StorageBucketManager_Delete_Method = 4519,
   kNavigatorUAData_GetHighEntropyValues = 4520,
+  kSchedulerYield = 4521,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/controller/blink_initializer.cc b/third_party/blink/renderer/controller/blink_initializer.cc
index 98b4da1..f2063994 100644
--- a/third_party/blink/renderer/controller/blink_initializer.cc
+++ b/third_party/blink/renderer/controller/blink_initializer.cc
@@ -276,7 +276,7 @@
   HighestPmfReporter::Initialize(main_thread_task_runner);
 #endif
 
-#if BUILDFLAG(IS_ANDROID) && !defined(ARCH_CPU_64_BITS)
+#if BUILDFLAG(IS_ANDROID)
   // Initialize PrivateMemoryFootprintProvider to start providing the value
   // for the browser process.
   PrivateMemoryFootprintProvider::Initialize(main_thread_task_runner);
diff --git a/third_party/blink/renderer/core/frame/local_dom_window_test.cc b/third_party/blink/renderer/core/frame/local_dom_window_test.cc
index b8332ea..17a3102 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window_test.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window_test.cc
@@ -251,14 +251,8 @@
   }
 }
 
-#if BUILDFLAG(IS_IOS)
-// TODO(crbug.com/1141478)
-#define MAYBE_CSPForWorld DISABLED_CSPForWorld
-#else
-#define MAYBE_CSPForWorld CSPForWorld
-#endif  // BUILDFLAG(IS_IOS)
 // Tests ExecutionContext::GetContentSecurityPolicyForCurrentWorld().
-TEST_F(PageTestBase, MAYBE_CSPForWorld) {
+TEST_F(PageTestBase, CSPForWorld) {
   using ::testing::ElementsAre;
 
   // Set a CSP for the main world.
diff --git a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
index a1191f1..2bd4dba 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_menu_element.cc
@@ -344,12 +344,14 @@
 void HTMLSelectMenuElement::setValue(const String& value, bool send_events) {
   // Find the option with value matching the given parameter and make it the
   // current selection.
+  HTMLOptionElement* selected_option = nullptr;
   for (auto& option : option_parts_) {
     if (option->value() == value) {
-      SetSelectedOption(option);
+      selected_option = option;
       break;
     }
   }
+  SetSelectedOption(selected_option);
 }
 
 bool HTMLSelectMenuElement::open() const {
diff --git a/third_party/blink/renderer/core/layout/layout_theme_test.cc b/third_party/blink/renderer/core/layout/layout_theme_test.cc
index f34ac03..345101df 100644
--- a/third_party/blink/renderer/core/layout/layout_theme_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_theme_test.cc
@@ -42,13 +42,7 @@
   return element->GetComputedStyle()->OutlineStyle();
 }
 
-#if BUILDFLAG(IS_IOS)
-// TODO(crbug.com/1141478)
-#define MAYBE_ChangeFocusRingColor DISABLED_ChangeFocusRingColor
-#else
-#define MAYBE_ChangeFocusRingColor ChangeFocusRingColor
-#endif  // BUILDFLAG(IS_IOS)
-TEST_F(LayoutThemeTest, MAYBE_ChangeFocusRingColor) {
+TEST_F(LayoutThemeTest, ChangeFocusRingColor) {
   SetHtmlInnerHTML("<span id=span tabIndex=0>Span</span>");
 
   Element* span = GetDocument().getElementById(AtomicString("span"));
diff --git a/third_party/blink/renderer/core/navigation_api/navigate_event.cc b/third_party/blink/renderer/core/navigation_api/navigate_event.cc
index 5c9771a..28328b6d 100644
--- a/third_party/blink/renderer/core/navigation_api/navigate_event.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigate_event.cc
@@ -5,9 +5,11 @@
 #include "third_party/blink/renderer/core/navigation_api/navigate_event.h"
 
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigate_event_init.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_handler.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_intercept_options.h"
+#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/dom/element.h"
@@ -24,6 +26,25 @@
 
 namespace blink {
 
+enum class ResolveType { kFulfill, kReject };
+class NavigateEvent::Reaction final : public ScriptFunction::Callable {
+ public:
+  Reaction(NavigateEvent* navigate_event, ResolveType resolve_type)
+      : navigate_event_(navigate_event), resolve_type_(resolve_type) {}
+  void Trace(Visitor* visitor) const final {
+    ScriptFunction::Callable::Trace(visitor);
+    visitor->Trace(navigate_event_);
+  }
+  ScriptValue Call(ScriptState*, ScriptValue value) final {
+    navigate_event_->ReactDone(value, resolve_type_ == ResolveType::kFulfill);
+    return ScriptValue();
+  }
+
+ private:
+  Member<NavigateEvent> navigate_event_;
+  ResolveType resolve_type_;
+};
+
 NavigateEvent::NavigateEvent(ExecutionContext* context,
                              const AtomicString& type,
                              NavigateEventInit* init)
@@ -165,22 +186,77 @@
   }
 }
 
-ScriptPromise NavigateEvent::GetReactionPromiseAll(ScriptState* script_state) {
+void NavigateEvent::React(ScriptState* script_state) {
   CHECK(navigation_action_handlers_list_.empty());
+
+  ScriptPromise promise;
   if (!navigation_action_promises_list_.empty()) {
-    return ScriptPromise::All(script_state, navigation_action_promises_list_);
+    promise =
+        ScriptPromise::All(script_state, navigation_action_promises_list_);
+  } else {
+    // There is a subtle timing difference between the fast-path for zero
+    // promises and the path for 1+ promises, in both spec and implementation.
+    // In most uses of ScriptPromise::All / the Web IDL spec's "wait for all",
+    // this does not matter. However for us there are so many events and promise
+    // handlers firing around the same time (navigatesuccess, committed promise,
+    // finished promise, ...) that the difference is pretty easily observable by
+    // web developers and web platform tests. So, let's make sure we always go
+    // down the 1+ promises path.
+    promise = ScriptPromise::All(
+        script_state, HeapVector<ScriptPromise>(
+                          {ScriptPromise::CastUndefined(script_state)}));
   }
-  // There is a subtle timing difference between the fast-path for zero
-  // promises and the path for 1+ promises, in both spec and implementation.
-  // In most uses of ScriptPromise::All / the Web IDL spec's "wait for all",
-  // this does not matter. However for us there are so many events and promise
-  // handlers firing around the same time (navigatesuccess, committed promise,
-  // finished promise, ...) that the difference is pretty easily observable by
-  // web developers and web platform tests. So, let's make sure we always go
-  // down the 1+ promises path.
-  return ScriptPromise::All(
-      script_state,
-      HeapVector<ScriptPromise>({ScriptPromise::CastUndefined(script_state)}));
+
+  promise.Then(MakeGarbageCollected<ScriptFunction>(
+                   script_state,
+                   MakeGarbageCollected<Reaction>(this, ResolveType::kFulfill)),
+               MakeGarbageCollected<ScriptFunction>(
+                   script_state,
+                   MakeGarbageCollected<Reaction>(this, ResolveType::kReject)));
+
+  if (HasNavigationActions() && DomWindow()) {
+    if (AXObjectCache* cache =
+            DomWindow()->document()->ExistingAXObjectCache()) {
+      cache->HandleLoadStart(DomWindow()->document());
+    }
+  }
+}
+
+void NavigateEvent::ReactDone(ScriptValue value, bool did_fulfill) {
+  CHECK_NE(intercept_state_, InterceptState::kIntercepted);
+  CHECK_NE(intercept_state_, InterceptState::kFinished);
+  if (signal_->aborted()) {
+    return;
+  }
+
+  LocalDOMWindow* window = DomWindow();
+  CHECK_EQ(this, window->navigation()->ongoing_navigate_event_);
+  window->navigation()->ongoing_navigate_event_ = nullptr;
+
+  if (intercept_state_ != InterceptState::kNone) {
+    PotentiallyResetTheFocus();
+    if (did_fulfill) {
+      PotentiallyProcessScrollBehavior();
+    }
+    intercept_state_ = InterceptState::kFinished;
+  }
+
+  if (did_fulfill) {
+    window->navigation()->DidFinishOngoingNavigation();
+  } else {
+    window->navigation()->DidFailOngoingNavigation(value);
+  }
+
+  if (HasNavigationActions()) {
+    if (LocalFrame* frame = window->GetFrame()) {
+      frame->Loader().DidFinishNavigation(
+          did_fulfill ? FrameLoader::NavigationFinishState::kSuccess
+                      : FrameLoader::NavigationFinishState::kFailure);
+    }
+    if (AXObjectCache* cache = window->document()->ExistingAXObjectCache()) {
+      cache->HandleLoadComplete(window->document());
+    }
+  }
 }
 
 void NavigateEvent::FinalizeNavigationActionPromisesList() {
@@ -257,19 +333,6 @@
   ProcessScrollBehavior();
 }
 
-void NavigateEvent::Finish(bool did_fulfill) {
-  CHECK_NE(intercept_state_, InterceptState::kIntercepted);
-  CHECK_NE(intercept_state_, InterceptState::kFinished);
-  if (intercept_state_ == InterceptState::kNone) {
-    return;
-  }
-  PotentiallyResetTheFocus();
-  if (did_fulfill) {
-    PotentiallyProcessScrollBehavior();
-  }
-  intercept_state_ = InterceptState::kFinished;
-}
-
 void NavigateEvent::PotentiallyProcessScrollBehavior() {
   CHECK(intercept_state_ == InterceptState::kCommitted ||
         intercept_state_ == InterceptState::kScrolled);
diff --git a/third_party/blink/renderer/core/navigation_api/navigate_event.h b/third_party/blink/renderer/core/navigation_api/navigate_event.h
index de983aea..9534696 100644
--- a/third_party/blink/renderer/core/navigation_api/navigate_event.h
+++ b/third_party/blink/renderer/core/navigation_api/navigate_event.h
@@ -64,12 +64,10 @@
   void intercept(NavigationInterceptOptions*, ExceptionState&);
 
   void DoCommit();
+  void React(ScriptState* script_state);
 
   void scroll(ExceptionState&);
 
-  void Finish(bool did_fulfill);
-
-  ScriptPromise GetReactionPromiseAll(ScriptState*);
   bool HasNavigationActions() const {
     return intercept_state_ != InterceptState::kNone;
   }
@@ -88,6 +86,9 @@
   void PotentiallyProcessScrollBehavior();
   void ProcessScrollBehavior();
 
+  class Reaction;
+  void ReactDone(ScriptValue, bool did_fulfill);
+
   String navigation_type_;
   Member<NavigationDestination> destination_;
   bool can_intercept_;
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.cc b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
index bcd140e..6e1074ed 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
@@ -10,7 +10,6 @@
 #include "third_party/blink/public/mojom/frame/frame.mojom-blink.h"
 #include "third_party/blink/public/web/web_frame_load_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/capture_source_location.h"
-#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -22,7 +21,6 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_result.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_transition.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_navigation_update_current_entry_options.h"
-#include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
 #include "third_party/blink/renderer/core/dom/abort_signal.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/event_target_names.h"
@@ -48,72 +46,6 @@
 
 namespace blink {
 
-class NavigateReaction final : public ScriptFunction::Callable {
- public:
-  enum class ResolveType { kFulfill, kReject };
-  static void React(ScriptState* script_state, NavigateEvent* navigate_event) {
-    navigate_event->GetReactionPromiseAll(script_state)
-        .Then(MakeGarbageCollected<ScriptFunction>(
-                  script_state, MakeGarbageCollected<NavigateReaction>(
-                                    navigate_event, ResolveType::kFulfill)),
-              MakeGarbageCollected<ScriptFunction>(
-                  script_state, MakeGarbageCollected<NavigateReaction>(
-                                    navigate_event, ResolveType::kReject)));
-
-    if (navigate_event->HasNavigationActions()) {
-      auto* window = LocalDOMWindow::From(script_state);
-      CHECK(window);
-      if (AXObjectCache* cache = window->document()->ExistingAXObjectCache())
-        cache->HandleLoadStart(window->document());
-    }
-  }
-
-  NavigateReaction(NavigateEvent* navigate_event, ResolveType resolve_type)
-      : navigate_event_(navigate_event), resolve_type_(resolve_type) {}
-
-  void Trace(Visitor* visitor) const final {
-    ScriptFunction::Callable::Trace(visitor);
-    visitor->Trace(navigate_event_);
-  }
-
-  ScriptValue Call(ScriptState* script_state, ScriptValue value) final {
-    auto* window = LocalDOMWindow::From(script_state);
-    CHECK(window);
-    if (navigate_event_->signal()->aborted()) {
-      return ScriptValue();
-    }
-
-    NavigationApi* navigation_api = window->navigation();
-    navigation_api->ongoing_navigate_event_ = nullptr;
-
-    navigate_event_->Finish(resolve_type_ == ResolveType::kFulfill);
-
-    if (resolve_type_ == ResolveType::kFulfill) {
-      navigation_api->DidFinishOngoingNavigation();
-    } else {
-      navigation_api->DidFailOngoingNavigation(value);
-    }
-
-    if (navigate_event_->HasNavigationActions()) {
-      if (LocalFrame* frame = window->GetFrame()) {
-        frame->Loader().DidFinishNavigation(
-            resolve_type_ == ResolveType::kFulfill
-                ? FrameLoader::NavigationFinishState::kSuccess
-                : FrameLoader::NavigationFinishState::kFailure);
-      }
-      if (AXObjectCache* cache = window->document()->ExistingAXObjectCache()) {
-        cache->HandleLoadComplete(window->document());
-      }
-    }
-
-    return ScriptValue();
-  }
-
- private:
-  Member<NavigateEvent> navigate_event_;
-  ResolveType resolve_type_;
-};
-
 template <typename... DOMExceptionArgs>
 NavigationResult* EarlyErrorResult(ScriptState* script_state,
                                    DOMExceptionArgs&&... args) {
@@ -724,15 +656,8 @@
   InformAboutCanceledNavigation();
   CHECK(window_);
 
-  const KURL& current_url = window_->Url();
-
-  const String& key = params->destination_item
-                          ? params->destination_item->GetNavigationApiKey()
-                          : String();
-  PromoteUpcomingNavigationToOngoing(key);
-
   if (HasEntriesAndEventsDisabled()) {
-    // This assertions holds because:
+    // These assertions holds because:
     // * back()/forward()/traverseTo() immediately fail when
     //   `HasEntriesAndEventsDisabled()` is false, because current_entry_index_
     //   will be permanently -1.
@@ -741,23 +666,31 @@
     //   promote to `ongoing_navigation_`.
     // * non-NavigationApi navigations never create an upcoming navigation.
     CHECK(!ongoing_navigation_);
+    CHECK(!upcoming_non_traversal_navigation_);
+    CHECK(upcoming_traversals_.empty());
     return DispatchResult::kContinue;
   }
 
-  LocalFrame* frame = window_->GetFrame();
-  auto* script_state = ToScriptStateForMainWorld(frame);
-  ScriptState::Scope scope(script_state);
-
+  const String& key = params->destination_item
+                          ? params->destination_item->GetNavigationApiKey()
+                          : String();
   if (params->frame_load_type == WebFrameLoadType::kBackForward &&
       params->event_type == NavigateEventType::kFragment &&
       !keys_to_indices_.Contains(key)) {
     // This same document history traversal was preempted by another navigation
     // that removed this entry from the back/forward list. Proceeding will leave
     // entries_ out of sync with the browser process.
-    AbortOngoingNavigation(script_state);
+    TraverseCancelled(
+        key, mojom::blink::TraverseCancelledReason::kAbortedBeforeCommit);
     return DispatchResult::kAbort;
   }
 
+  PromoteUpcomingNavigationToOngoing(key);
+
+  LocalFrame* frame = window_->GetFrame();
+  auto* script_state = ToScriptStateForMainWorld(frame);
+  ScriptState::Scope scope(script_state);
+
   auto* init = NavigateEventInit::Create();
   const String& navigation_type =
       DetermineNavigationType(params->frame_load_type);
@@ -795,13 +728,13 @@
                       should_allow_traversal_cancellation);
   init->setCanIntercept(
       CanChangeToUrlForHistoryApi(params->url, window_->GetSecurityOrigin(),
-                                  current_url) &&
+                                  window_->Url()) &&
       (params->event_type != NavigateEventType::kCrossDocument ||
        params->frame_load_type != WebFrameLoadType::kBackForward));
   init->setHashChange(
       params->event_type == NavigateEventType::kFragment &&
-      params->url != current_url &&
-      EqualIgnoringFragmentIdentifier(params->url, current_url));
+      params->url != window_->Url() &&
+      EqualIgnoringFragmentIdentifier(params->url, window_->Url()));
 
   init->setUserInitiated(params->involvement !=
                          UserNavigationInvolvement::kNone);
@@ -852,7 +785,7 @@
 
   if (navigate_event->HasNavigationActions() ||
       params->event_type != NavigateEventType::kCrossDocument) {
-    NavigateReaction::React(script_state, navigate_event);
+    navigate_event->React(script_state);
   }
 
   // Note: we cannot clean up ongoing_navigation_ for cross-document
@@ -968,18 +901,16 @@
 }
 
 void NavigationApi::AbortOngoingNavigation(ScriptState* script_state) {
+  CHECK(ongoing_navigate_event_);
   ScriptValue error = ScriptValue::From(
       script_state,
       MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError,
                                          "Navigation was aborted"));
-
-  if (ongoing_navigate_event_) {
-    if (ongoing_navigate_event_->IsBeingDispatched())
-      ongoing_navigate_event_->preventDefault();
-    ongoing_navigate_event_->signal()->SignalAbort(script_state, error);
-    ongoing_navigate_event_ = nullptr;
+  if (ongoing_navigate_event_->IsBeingDispatched()) {
+    ongoing_navigate_event_->preventDefault();
   }
-
+  ongoing_navigate_event_->signal()->SignalAbort(script_state, error);
+  ongoing_navigate_event_ = nullptr;
   DidFailOngoingNavigation(error);
 }
 
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.h b/third_party/blink/renderer/core/navigation_api/navigation_api.h
index 09bd88f..60abf44 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.h
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.h
@@ -132,7 +132,7 @@
   void Trace(Visitor*) const final;
 
  private:
-  friend class NavigateReaction;
+  friend class NavigateEvent;
   NavigationHistoryEntry* GetEntryForRestore(
       const mojom::blink::NavigationApiHistoryEntryPtr&);
   void PopulateKeySet();
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
index da924bb7..c8c7bec 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
@@ -92,16 +92,17 @@
   auto state = paint_controller.CurrentPaintChunkProperties();
   if (const auto* filter = properties.Filter()) {
     state.SetEffect(*filter);
-    if (const auto* filter_clip = properties.PixelMovingFilterClipExpander())
-      state.SetClip(*filter_clip);
   } else if (const auto* effect = properties.Effect()) {
     state.SetEffect(*effect);
   }
-
-  if (const auto* mask_clip = properties.MaskClip())
+  if (const auto* filter_clip = properties.PixelMovingFilterClipExpander()) {
+    state.SetClip(*filter_clip);
+  } else if (const auto* mask_clip = properties.MaskClip()) {
     state.SetClip(*mask_clip);
-  else if (const auto* clip_path_clip = properties.ClipPathClip())
+  } else if (const auto* clip_path_clip = properties.ClipPathClip()) {
     state.SetClip(*clip_path_clip);
+  }
+
   scoped_paint_chunk_properties_.emplace(
       paint_controller, state, display_item_client_,
       DisplayItem::PaintPhaseToSVGEffectType(paint_info_.phase));
diff --git a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
index 1ecc3774..2d1e146 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
@@ -54,7 +54,7 @@
   CHECK(context->GetScheduler());
   CreateFixedPriorityTaskQueues(context, WebSchedulingQueueType::kTaskQueue,
                                 fixed_priority_task_queues_);
-  if (RuntimeEnabledFeatures::SchedulerYieldEnabled()) {
+  if (RuntimeEnabledFeatures::SchedulerYieldEnabled(context)) {
     CreateFixedPriorityTaskQueues(context,
                                   WebSchedulingQueueType::kContinuationQueue,
                                   fixed_priority_continuation_queues_);
@@ -246,7 +246,8 @@
   DOMTaskSignal* inherited_signal = nullptr;
   if (absl::holds_alternative<InheritOption>(signal_option) ||
       absl::holds_alternative<InheritOption>(priority_option)) {
-    CHECK(RuntimeEnabledFeatures::SchedulerYieldEnabled());
+    CHECK(RuntimeEnabledFeatures::SchedulerYieldEnabled(
+        ExecutionContext::From(script_state)));
     if (auto* inherited_state =
             ScriptWrappableTaskState::GetCurrent(script_state)) {
       inherited_signal = inherited_state->GetSignal();
diff --git a/third_party/blink/renderer/modules/scheduler/dom_task.cc b/third_party/blink/renderer/modules/scheduler/dom_task.cc
index e121979..d8c1de0 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_task.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_task.cc
@@ -208,7 +208,8 @@
         script_state, parent_task_id_,
         scheduler::TaskAttributionTracker::TaskScopeType::kSchedulerPostTask,
         signal_);
-  } else if (RuntimeEnabledFeatures::SchedulerYieldEnabled()) {
+  } else if (RuntimeEnabledFeatures::SchedulerYieldEnabled(
+                 ExecutionContext::From(script_state))) {
     ScriptWrappableTaskState::SetCurrent(
         script_state, MakeGarbageCollected<ScriptWrappableTaskState>(
                           scheduler::TaskAttributionId(), signal_));
diff --git a/third_party/blink/renderer/modules/scheduler/scheduler.idl b/third_party/blink/renderer/modules/scheduler/scheduler.idl
index 4607222..da19bad 100644
--- a/third_party/blink/renderer/modules/scheduler/scheduler.idl
+++ b/third_party/blink/renderer/modules/scheduler/scheduler.idl
@@ -19,7 +19,7 @@
     ImplementedAs=DOMScheduler
 ] interface Scheduler {
     [CallWith=ScriptState, MeasureAs=SchedulerPostTask, RaisesException] Promise<any> postTask(SchedulerPostTaskCallback callback, optional SchedulerPostTaskOptions options = {});
-    [RuntimeEnabled=SchedulerYield, CallWith=ScriptState, RaisesException] Promise<undefined> yield(optional SchedulerYieldOptions options = {});
+    [RuntimeEnabled=SchedulerYield, MeasureAs=SchedulerYield, CallWith=ScriptState, RaisesException] Promise<undefined> yield(optional SchedulerYieldOptions options = {});
     [RuntimeEnabled=UnexposedTaskIds, CallWith=ScriptState, Exposed=Window] readonly attribute unsigned long taskId;
     [RuntimeEnabled=UnexposedTaskIds, CallWith=ScriptState, Exposed=Window] AncestorStatus isAncestor(unsigned long parentId);
 };
diff --git a/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc b/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
index 004eb662..87c2d3a2 100644
--- a/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
+++ b/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
@@ -49,7 +49,8 @@
     if (auto* tracker =
             ThreadScheduler::Current()->GetTaskAttributionTracker()) {
       DOMTaskSignal* signal = nullptr;
-      if (RuntimeEnabledFeatures::SchedulerYieldEnabled()) {
+      if (RuntimeEnabledFeatures::SchedulerYieldEnabled(
+              ExecutionContext::From(script_state))) {
         auto* context = ExecutionContext::From(script_state);
         CHECK(context);
         signal = DOMScheduler::scheduler(*context)->GetFixedPriorityTaskSignal(
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 73d9152a..4019776 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2901,6 +2901,7 @@
     },
     {
       name: "SchedulerYield",
+      origin_trial_feature_name: "SchedulerYield",
       depends_on: ["AbortSignalComposition"],
       status: "experimental",
     },
diff --git a/third_party/blink/web_tests/FlagSpecificConfig b/third_party/blink/web_tests/FlagSpecificConfig
index eb3eb5b..0feba4f0 100644
--- a/third_party/blink/web_tests/FlagSpecificConfig
+++ b/third_party/blink/web_tests/FlagSpecificConfig
@@ -53,7 +53,8 @@
           "--disable-dawn-features=disallow_unsafe_apis",
           "--enable-webgpu-developer-features",
           "--use-gpu-in-tests",
-          "--enable-accelerated-2d-canvas"
+          "--enable-accelerated-2d-canvas",
+          "--use-webgpu-power-preference=default-high-performance"
     ],
     "comments": [
           "--enable-unsafe-webgpu makes sure WebGPU is enabled even if it",
@@ -66,7 +67,9 @@
           "needed to tell Dawn which ANGLE adapter Chromium is using. This",
           "helps match the test behavior with the browser.",
           "--enable-accelerated-2d-canvas ensures that the canvas reference",
-          "tests are using GPU rather than CPU"
+          "tests are using GPU rather than CPU",
+          "--use-webgpu-power-preference=default-high-performance indicates",
+          "it uses high-performance gpu when not explicitly specified"
     ]
   },
   {
@@ -128,7 +131,8 @@
           "--disable-dawn-features=disallow_unsafe_apis",
           "--enable-webgpu-developer-features",
           "--use-gpu-in-tests",
-          "--enable-accelerated-2d-canvas"
+          "--enable-accelerated-2d-canvas",
+          "--use-webgpu-power-preference=default-high-performance"
     ],
     "comments": [
           "--enable-unsafe-webgpu makes sure WebGPU is enabled even if it",
@@ -141,7 +145,9 @@
           "needed to tell Dawn which ANGLE adapter Chromium is using. This",
           "helps match the test behavior with the browser.",
           "--enable-accelerated-2d-canvas ensures that the canvas reference",
-          "tests are using GPU rather than CPU"
+          "tests are using GPU rather than CPU",
+          "--use-webgpu-power-preference=default-high-performance indicates",
+          "it uses high-performance gpu when not explicitly specified"
     ]
   },
   {
@@ -152,7 +158,8 @@
           "--disable-dawn-features=disallow_unsafe_apis",
           "--enable-webgpu-developer-features",
           "--use-gpu-in-tests",
-          "--enable-accelerated-2d-canvas"
+          "--enable-accelerated-2d-canvas",
+          "--use-webgpu-power-preference=default-high-performance"
     ],
     "comments": [
           "--enable-unsafe-webgpu makes sure WebGPU is enabled even if it",
@@ -165,7 +172,9 @@
           "needed to tell Dawn which ANGLE adapter Chromium is using. This",
           "helps match the test behavior with the browser.",
           "--enable-accelerated-2d-canvas ensures that the canvas reference",
-          "tests are using GPU rather than CPU"
+          "tests are using GPU rather than CPU",
+          "--use-webgpu-power-preference=default-high-performance indicates",
+          "it uses high-performance gpu when not explicitly specified"
     ]
   }
 ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 029ec63..014564b4 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3714,9 +3714,6 @@
 # Sheriff failures 2017-07-03
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Pass Timeout ]
 
-# Crashes with CSSTransitionDiscrete enabled
-crbug.com/1399631 transitions/unanimatable-properties.html [ Crash Failure Pass Timeout ]
-
 # Tests failing when enabling new modern media controls
 crbug.com/832157 external/wpt/html/semantics/embedded-content/media-elements/track/track-element/track-cue-rendering-after-controls-added.html [ Crash Failure Pass Timeout ]
 crbug.com/832169 media/media-controls-fit-properly-while-zoomed.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-animations/transition-properties-not-animatable.html b/third_party/blink/web_tests/external/wpt/css/css-animations/transition-properties-not-animatable.html
new file mode 100644
index 0000000..288487b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-animations/transition-properties-not-animatable.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-duration-property">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-delay-property">
+<link rel="help" href="https://www.w3.org/TR/css-transitions-1/#transition-property-property">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/support/interpolation-testcommon.js"></script>
+
+<body>
+<script>
+test_not_animatable({
+  property: 'transition-duration',
+  from: '1s',
+  to: '2s',
+  underlying: '0s',
+});
+
+test_not_animatable({
+  property: 'transition-delay',
+  from: '1s',
+  to: '2s',
+  underlying: '0s',
+});
+
+test_not_animatable({
+  property: 'transition-property',
+  from: 'color',
+  to: 'background-color',
+  underlying: 'all',
+});
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html
index 3ba7da6b..8998a07 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/forms/the-selectmenu-element/selectmenu-value-selectedOption.tentative.html
@@ -65,7 +65,7 @@
   assert_equals(selectMenu0.value, "");
   assert_equals(selectMenu0.selectedOption, null);
   selectMenu0.value = "something";
-  assert_equals(selectMenu0.value, "", "Setting value should have no effect if there is no matching option");
+  assert_equals(selectMenu0.value, "", "If there is no matching option, selectmenu should be cleared");
   assert_equals(selectMenu0.selectedOption, null);
 }, "Test that HTMLSelectMenu with no options has empty string for value and null for selectedOption");
 
@@ -78,11 +78,31 @@
   assert_equals(selectMenu1.selectedOption, document.getElementById("selectMenu1-option3"));
 
   selectMenu1.value = "I'm a div with no part attr";
-  assert_equals(selectMenu1.value, "three", "Setting value should have no effect if there is no matching option");
-  assert_equals(selectMenu1.selectedOption, document.getElementById("selectMenu1-option3"));
+  assert_equals(selectMenu1.value, "", "If there is no matching option selectmenu should be cleared");
+  assert_equals(selectMenu1.selectedOption, null);
 }, "Test value and selectedOption with HTMLOptionElement element option parts");
 
 test(() => {
+  const selectMenu1 = document.getElementById("selectMenu1");
+  selectMenu1.value = "one";
+  assert_equals(selectMenu1.value, "one");
+
+  selectMenu1.value = null;
+  assert_equals(selectMenu1.value, "");
+  assert_equals(selectMenu1.selectedOption, null);
+}, "Test value and selectedOption when value is null");
+
+test(() => {
+  const selectMenu1 = document.getElementById("selectMenu1");
+  selectMenu1.value = "one";
+  assert_equals(selectMenu1.value, "one");
+
+  selectMenu1.value = undefined;
+  assert_equals(selectMenu1.value, "");
+  assert_equals(selectMenu1.selectedOption, null);
+}, "Test value and selectedOption when value is undefined");
+
+test(() => {
   const selectMenu2 = document.getElementById("selectMenu2");
   assert_equals(selectMenu2.value, "", "Non-HTMLOptionElements shouldn't be treated as option parts");
   assert_equals(selectMenu2.selectedOption, null);
diff --git a/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/forward-to-pruned-entry.html b/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/forward-to-pruned-entry.html
index b8bd36e..05f70c5 100644
--- a/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/forward-to-pruned-entry.html
+++ b/third_party/blink/web_tests/external/wpt/navigation-api/navigation-methods/forward-to-pruned-entry.html
@@ -14,7 +14,9 @@
   // Traverse forward then immediately do a same-document push. This will
   // truncate the back forward list, and by the time the traverse commits, the
   // destination key will no longer be present in navigation.entries(). The
-  // traverse should abort.
+  // traverse should abort. Because the traverse aborts before the navigate
+  // event fires, the navigateerror event should not fire.
+  navigation.onnavigateerror = t.unreached_func("navigateerror should not fire");
   let forward_value = navigation.forward();
   await navigation.navigate("#clobber").finished;
   assert_equals(navigation.currentEntry.index, 1);
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.html b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.html
new file mode 100644
index 0000000..ebd2d86
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="width: 100px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.txt
new file mode 100644
index 0000000..ff4862e4
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move-expected.txt
@@ -0,0 +1,15 @@
+{
+  "layers": [
+    {
+      "name": "Scrolling background of LayoutNGView #document",
+      "bounds": [800, 600],
+      "contentsOpaque": true,
+      "backgroundColor": "#FFFFFF",
+      "invalidations": [
+        [158, 8, 100, 100],
+        [8, 8, 100, 100]
+      ]
+    }
+  ]
+}
+
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move.html b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move.html
new file mode 100644
index 0000000..4e2ab8a
--- /dev/null
+++ b/third_party/blink/web_tests/paint/invalidation/svg/svg-filter-with-clip-path-move.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="../resources/text-based-repaint.js"></script>
+<svg style="width: 400px; height: 400px">
+  <clipPath id="clip">
+    <rect width="500" height="500"/>
+  </clipPath>
+  <rect id="rect" x="150" width="100" height="50" fill="green"
+        style="clip-path: url(#clip); filter: drop-shadow(0 50px 0 green)"/>
+</svg>
+<script>
+function repaintTest() {
+  rect.setAttribute("x", "0");
+}
+onload = runRepaintAndPixelTest;
+</script>
diff --git a/third_party/blink/web_tests/transitions/unanimatable-properties-expected.txt b/third_party/blink/web_tests/transitions/unanimatable-properties-expected.txt
deleted file mode 100644
index 00fc34b..0000000
--- a/third_party/blink/web_tests/transitions/unanimatable-properties-expected.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-PASS - "counter-increment" property for "box" element at 0.1s saw something close to: counter 2
-PASS - "overflow" property for "box" element at 0.1s saw something close to: hidden
-PASS - "transition-duration" property for "box" element at 0.1s saw something close to: 0.5s
-PASS - "top" property for "box" element at 0.25s saw something close to: 25
-PASS - "counter-increment" property for "box" element at 0.4s saw something close to: counter 2
-PASS - "overflow" property for "box" element at 0.4s saw something close to: hidden
-PASS - "transition-duration" property for "box" element at 0.4s saw something close to: 0.5s
-
diff --git a/third_party/blink/web_tests/transitions/unanimatable-properties.html b/third_party/blink/web_tests/transitions/unanimatable-properties.html
deleted file mode 100644
index 220abdd6..0000000
--- a/third_party/blink/web_tests/transitions/unanimatable-properties.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-<head>
-  <style>
-    #box {
-      position: absolute;
-      height: 100px;
-      width: 100px;
-      left: 0px;
-      top: 0px;
-      background-color: blue;
-      transition-duration: 1s;
-      transition-timing-function: linear;
-      transition-property: top, counter-increment, overflow, transition-duration;
-    }
-  </style>
-  <script src="../animations/resources/animation-test-helpers.js"></script>
-  <script type="text/javascript">
-    const expectedValues = [
-      // [time, element-id, property, expected-value, tolerance]
-      [0.10, "box", "counter-increment", "counter 2", 0],
-      [0.10, "box", "overflow", "hidden", 0],
-      [0.10, "box", "transition-duration", "0.5s", 0],
-      [0.25, "box", "top", 25, 5],
-      [0.40, "box", "counter-increment", "counter 2", 0],
-      [0.40, "box", "overflow", "hidden", 0],
-      [0.40, "box", "transition-duration", "0.5s", 0],
-    ];
-
-    function setupTest()
-    {
-      box.style.top = "50px";
-      box.style.overflow = "hidden";
-      box.style.counterIncrement = "counter 2";
-      box.style.transitionDuration = "0.5s";
-    }
-
-    runTransitionTest(expectedValues, setupTest);
-  </script>
-</head>
-<body>
-<p>Tests that transitions ignore unanimatable properties.</p>
-<div id="box"></div>
-<div id="result"></div>
-</body>
-</html>
diff --git a/tools/android/avd/proto/creation/generic_android32_foldable.textpb b/tools/android/avd/proto/creation/generic_android32_foldable.textpb
index 9157f7a..41e1e5e 100644
--- a/tools/android/avd/proto/creation/generic_android32_foldable.textpb
+++ b/tools/android/avd/proto/creation/generic_android32_foldable.textpb
@@ -6,13 +6,13 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "0EgkhrUNQp2T1opBdG5kmtrz6b-fKKwfeivwy73nGicC"  # 31.3.10
+  version: "vr0Vf27dh76-6sz87fx6tF-3br8crNIgxByTaYayuX4C"  # 32.1.12
   dest_path: "generic_android32_foldable"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-32/google_apis/x86_64"
-  version: "KjjO5uts0yb_xXLds-ChSenU5VebO2OkXkSdt5eBuYAC"  # r3, S2B2.211203.006
+  version: "WhbaU7Z2Is60EoZ3jB-5QHnlFy-mXeUekoUgrLa_BqcC"  # r5, SE1B.220616.004
   dest_path: "generic_android32_foldable"
 }
 system_image_name: "system-images;android-32;google_apis;x86_64"
diff --git a/tools/android/avd/proto/creation/generic_android33.textpb b/tools/android/avd/proto/creation/generic_android33.textpb
index 58a95d8..7a19f61 100644
--- a/tools/android/avd/proto/creation/generic_android33.textpb
+++ b/tools/android/avd/proto/creation/generic_android33.textpb
@@ -6,13 +6,13 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "ZjmabmwvcQ8kZtLsnx6rj40RlCglPwP-PDPEb5fWrEAC"  # 31.3.14
+  version: "vr0Vf27dh76-6sz87fx6tF-3br8crNIgxByTaYayuX4C"  # 32.1.12
   dest_path: "generic_android33"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-33/google_apis/x86_64"
-  version: "8KxiJ6WNnLxlTmM-HntbcSidjUl31gWt6lnAbngsfx0C"  # r8, TE1A.220922.012
+  version: "1TealwPsZ5QUC9ZhKI9-nbMjZ1cFouZQ-CzOv0varQcC"  # r9, TE1A.220922.021
   dest_path: "generic_android33"
 }
 system_image_name: "system-images;android-33;google_apis;x86_64"
diff --git a/tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb b/tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb
index db7d1da..432f54a 100644
--- a/tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb
+++ b/tools/android/avd/proto/creation/generic_playstore_android32_foldable.textpb
@@ -6,13 +6,13 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "OVSG6UsN_z5imRD1QeccG1WvIigGZIgWByGOktKb1NYC"  # 31.2.9
+  version: "vr0Vf27dh76-6sz87fx6tF-3br8crNIgxByTaYayuX4C"  # 32.1.12
   dest_path: "generic_playstore_android32_foldable"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-32/google_apis_playstore/x86_64"
-  version: "lwWeSNtlALCSilzFMePHcn_tNRi6JlhuFkO460tVeqQC"  # r3, S2B2.211203.006
+  version: "xoJXp1MgQDF88Ugr13tibZNiD66qrlaTiGw1BQWVENQC"  # r3, S2B2.211203.006
   dest_path: "generic_playstore_android32_foldable"
 }
 system_image_name: "system-images;android-32;google_apis_playstore;x86_64"
diff --git a/tools/android/avd/proto/creation/generic_playstore_android33.textpb b/tools/android/avd/proto/creation/generic_playstore_android33.textpb
index 2f7be29..3cefef2 100644
--- a/tools/android/avd/proto/creation/generic_playstore_android33.textpb
+++ b/tools/android/avd/proto/creation/generic_playstore_android33.textpb
@@ -6,13 +6,13 @@
 
 emulator_package {
   package_name: "chromium/third_party/android_sdk/public/emulator"
-  version: "9lGp8nTUCRRWGMnI_96HcKfzjnxEJKUcfvfwmA3wXNkC"  # 31.2.10
+  version: "vr0Vf27dh76-6sz87fx6tF-3br8crNIgxByTaYayuX4C"  # 32.1.12
   dest_path: "generic_playstore_android33"
 }
 
 system_image_package {
   package_name: "chromium/third_party/android_sdk/public/system-images/android-33/google_apis_playstore/x86_64"
-  version: "ndvYifhxEbUfsJ0FagczmGFFLE0Lp9ZxBif9P8seZS8C"  # beta 3
+  version: "zNsYsNmjunAoNpRur3d0lbIaUAbnPHy4xqojAFG5gHMC"  # r7, TE1A.220922.010
   dest_path: "generic_playstore_android33"
 }
 system_image_name: "system-images;android-33;google_apis_playstore;x86_64"
diff --git a/tools/crates/gnrt/gn.rs b/tools/crates/gnrt/gn.rs
index dd9804d..8210d498 100644
--- a/tools/crates/gnrt/gn.rs
+++ b/tools/crates/gnrt/gn.rs
@@ -13,7 +13,7 @@
 
 use std::collections::HashMap;
 use std::convert::From;
-use std::fmt::{self, Write};
+use std::fmt::{self, Display, Write};
 use std::path::Path;
 
 /// Describes a BUILD.gn file for a single crate epoch. Each file may have
@@ -529,19 +529,14 @@
     writeln!(writer, "edition = \"{}\"", details.edition)?;
     writeln!(writer, "cargo_pkg_version = \"{}\"", details.cargo_pkg_version)?;
     if let Some(authors) = &details.cargo_pkg_authors {
-        write!(writer, "cargo_pkg_authors = \"")?;
-        write!(Escaper(&mut writer), "{authors}")?;
-        writeln!(writer, "\"")?;
+        writeln!(writer, "cargo_pkg_authors = \"{}\"", escaped(authors))?;
     }
     writeln!(writer, "cargo_pkg_name = \"{}\"", details.cargo_pkg_name)?;
     if let Some(description) = &details.cargo_pkg_description {
-        // Remove the trailing newline, which unattractively comes out as a
-        // trailing space. Escaper can't do this because, as a Write
-        // implementation, it does not know where the end of input will be.
-        let description = description.trim_end();
-        write!(writer, "cargo_pkg_description = \"")?;
-        write!(Escaper(&mut writer), "{description}")?;
-        writeln!(writer, "\"")?;
+        // Use trim_end() to remove the trailing newline, which unattractively
+        // comes out as a space. escaped() can't do this because its internal
+        // Write implementation does not know where the end of input will be.
+        writeln!(writer, "cargo_pkg_description = \"{}\"", escaped(description.trim_end()))?;
     }
     writeln!(writer, "library_configs -= [ \"//build/config/compiler:chromium_code\" ]")?;
     writeln!(writer, "library_configs += [ \"//build/config/compiler:no_chromium_code\" ]")?;
@@ -652,9 +647,7 @@
 ) -> fmt::Result {
     writeln!(writer, "[")?;
     for item in items.into_iter() {
-        write!(writer, "\"")?;
-        write!(Escaper(&mut writer), "{item}")?;
-        writeln!(writer, "\",")?;
+        writeln!(writer, r#""{}","#, escaped(item))?;
     }
     writeln!(writer, "]")
 }
@@ -665,28 +658,38 @@
 ) -> fmt::Result {
     writeln!(writer, "{{")?;
     for (left, right) in items.into_iter() {
-        write!(writer, "{left} = \"")?;
-        write!(Escaper(&mut writer), "{right}")?;
-        writeln!(writer, "\"")?;
+        writeln!(writer, r#"{left} = "{}""#, escaped(right))?;
     }
     writeln!(writer, "}}")
 }
 
-/// Wraps a `Write` and escapes special characters when written to. Suitable for
-/// outputting GN strings. Note that it does not escape '$', since we want to
-/// use GN "$var" syntax in some cases. Also due to an apparent bug in GN's
-/// output to Ninja files, we skip newlines.
+/// Wraps a `Display`-able type with another `Display` implementation that
+/// escapes all characters according to GN string rules.
+///
+/// Note that it does not escape '$', since we want to use GN "$var" syntax in
+/// some cases. Also due to an apparent bug in GN's output to Ninja files, we
+/// replace newlines with spaces.
 ///
 /// See https://gn.googlesource.com/gn/+/refs/heads/main/docs/language.md#Strings
-pub struct Escaper<W>(W);
+fn escaped<T: Display>(x: T) -> impl Display {
+    Escaped(x)
+}
 
-impl<W: Write> Escaper<W> {
-    pub fn new_for_testing(writer: W) -> Self {
-        Escaper(writer)
+pub fn escaped_for_testing<T: Display>(x: T) -> impl Display {
+    escaped(x)
+}
+
+struct Escaped<T>(T);
+
+impl<T: Display> Display for Escaped<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(EscapedWriter(f), "{}", self.0)
     }
 }
 
-impl<W: Write> Write for Escaper<W> {
+struct EscapedWriter<W>(W);
+
+impl<W: Write> Write for EscapedWriter<W> {
     fn write_str(&mut self, s: &str) -> fmt::Result {
         s.chars().try_for_each(|c| self.write_char(c))
     }
diff --git a/tools/crates/gnrt/gn_unittest.rs b/tools/crates/gnrt/gn_unittest.rs
index 0fe1e47..1350f40 100644
--- a/tools/crates/gnrt/gn_unittest.rs
+++ b/tools/crates/gnrt/gn_unittest.rs
@@ -396,15 +396,8 @@
 
 #[gtest(GnTest, StringEscaping)]
 fn test() {
-    fn escaped(s: &str) -> String {
-        use std::fmt::Write;
-        let mut out = String::new();
-        write!(Escaper::new_for_testing(&mut out), "{s}").unwrap();
-        out
-    }
-
-    expect_eq!("foo bar", escaped("foo bar"));
-    expect_eq!("foo bar ", escaped("foo\nbar\n"));
-    expect_eq!(r#"foo \"bar\""#, escaped(r#"foo "bar""#));
-    expect_eq!("foo 'bar'", escaped("foo 'bar'"));
+    expect_eq!("foo bar", format!("{}", escaped_for_testing("foo bar")));
+    expect_eq!("foo bar ", format!("{}", escaped_for_testing("foo\nbar\n")));
+    expect_eq!(r#"foo \"bar\""#, format!("{}", escaped_for_testing(r#"foo "bar""#)));
+    expect_eq!("foo 'bar'", format!("{}", escaped_for_testing("foo 'bar'")));
 }
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 6d181c0..18ae631 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -14142,6 +14142,19 @@
   <int value="2" label="Destructed Before Timeout"/>
 </enum>
 
+<enum name="CameraEffectError">
+  <int value="0" label="kNoError"/>
+  <int value="1" label="kGPUInitializationError"/>
+  <int value="2" label="kBufferAllocationError"/>
+  <int value="3" label="kReceivedFailedBuffer"/>
+  <int value="4" label="kSyncWaitTimeout"/>
+  <int value="5" label="kBufferRegistrationFailed"/>
+  <int value="6" label="kBufferUnregistrationFailed"/>
+  <int value="7" label="kGPUImageInitializationFailed"/>
+  <int value="8" label="kYUVConversionFailed"/>
+  <int value="9" label="kPipelineFailed"/>
+</enum>
+
 <enum name="CameraEffectType">
   <int value="0" label="kNone"/>
   <int value="1" label="kBlur"/>
@@ -17606,6 +17619,7 @@
   <int value="3" label="Virtual Keyboard"/>
   <int value="4" label="Unknown"/>
   <int value="5" label="Toast"/>
+  <int value="6" label="Ctrl+V Longpress"/>
 </enum>
 
 <enum name="ClipboardPastedImageUrls">
@@ -33388,6 +33402,28 @@
   <int value="4" label="No result within timout"/>
 </enum>
 
+<enum name="EolIncentiveButtonType">
+  <int value="0" label="Notification Offer Approaching"/>
+  <int value="1" label="Notification No Offer Approaching"/>
+  <int value="2" label="Notification AboutUpdates Approaching"/>
+  <int value="3" label="Notification Offer Recently Passed"/>
+  <int value="4" label="Notification No Offer Recently Passed"/>
+  <int value="5" label="Notification AboutUpdates Recently Passed"/>
+  <int value="6" label="Notification Original LearnMore"/>
+  <int value="7" label="Notification Original Dismiss"/>
+  <int value="8" label="Quick Settings Offer Recently Passed"/>
+  <int value="9" label="Quick Settings No Offer Recently Passed"/>
+  <int value="10" label="Quick Settings No Offer Passed"/>
+</enum>
+
+<enum name="EolIncentiveShowSource">
+  <int value="0" label="Notification Approaching"/>
+  <int value="1" label="Notification Recently Passed"/>
+  <int value="2" label="Notification Original"/>
+  <int value="3" label="Quick Settings Recently Passed"/>
+  <int value="4" label="Quick Settings Passed"/>
+</enum>
+
 <enum name="EquivalentEffectiveAndWhiteListed">
   <int value="0" label="Different effective and whitelisted touch actions"/>
   <int value="1" label="Equivalent effective and whitelisted touch actions"/>
@@ -42541,6 +42577,7 @@
   <int value="4518" label="V8StorageBucketManager_Keys_Method"/>
   <int value="4519" label="V8StorageBucketManager_Delete_Method"/>
   <int value="4520" label="NavigatorUAData_GetHighEntropyValues"/>
+  <int value="4521" label="SchedulerYield"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
@@ -50230,6 +50267,11 @@
   <int value="11" label="kDriveSuggestion"/>
   <int value="12" label="kLocalSuggestion"/>
   <int value="13" label="kScreenRecordingGif"/>
+  <int value="14" label="kCameraAppPhoto"/>
+  <int value="15" label="kCameraAppScanJpg"/>
+  <int value="16" label="kCameraAppScanPdf"/>
+  <int value="17" label="kCameraAppVideoGif"/>
+  <int value="18" label="kCameraAppVideoMp4"/>
 </enum>
 
 <enum name="HoldingSpacePodAction">
@@ -51879,6 +51921,7 @@
   <int value="13" label="MULTIWORD_PREDICTION"/>
   <int value="14" label="MULTIWORD_COMPLETION"/>
   <int value="15" label="LONGPRESS_DIACRITICS"/>
+  <int value="16" label="LONGPRESS_CONTROL_V"/>
 </enum>
 
 <enum name="IMEAssistiveDisabledReason">
@@ -58895,6 +58938,7 @@
   <int value="-1872867546" label="EnumerateAudioDevices:disabled"/>
   <int value="-1872205507" label="ShelfNewUi:enabled"/>
   <int value="-1871552967" label="DesktopPWAsTabStrip:disabled"/>
+  <int value="-1871389939" label="DeskButton:enabled"/>
   <int value="-1871185948" label="VrLaunchIntents:disabled"/>
   <int value="-1870961970" label="enable-filemanager-mtp"/>
   <int value="-1869845022" label="force-show-update-menu-item"/>
@@ -60709,6 +60753,7 @@
   <int value="-883694393" label="SyncPseudoUSSSupervisedUsers:disabled"/>
   <int value="-883608641" label="enable-cros-action-recorder"/>
   <int value="-882434910" label="EnableAggregatedMlSearchRanking:enabled"/>
+  <int value="-881903516" label="DeskButton:disabled"/>
   <int value="-881477103" label="PageInfoAboutThisSiteNonEn:enabled"/>
   <int value="-881447505" label="ash-disable-shelf-model-synchronization"/>
   <int value="-881054479" label="WebAssemblyStreaming:disabled"/>
@@ -62386,6 +62431,7 @@
   <int value="46334141"
       label="FeatureNotificationGuideSkipCheckForLowEngagedUsers:enabled"/>
   <int value="48159177" label="reduced-referrer-granularity"/>
+  <int value="48666910" label="ClipboardHistoryLongpress:disabled"/>
   <int value="50643563" label="MBIMode:enabled"/>
   <int value="51793504" label="protect-sync-credential-on-reauth:disabled"/>
   <int value="52150780" label="OsSettingsPolymer3:disabled"/>
@@ -65564,6 +65610,7 @@
   <int value="1820445741" label="SwapAndroidShareHubRows:disabled"/>
   <int value="1820451991" label="enable-offline-auto-reload"/>
   <int value="1821723343" label="disable-saml-signin"/>
+  <int value="1822452137" label="ClipboardHistoryLongpress:enabled"/>
   <int value="1823102966" label="disable-ipc-flooding-protection"/>
   <int value="1823337908" label="SmartDim20190221:enabled"/>
   <int value="1824332134" label="EnableDnsProxy:disabled"/>
@@ -74910,6 +74957,16 @@
   <int value="2" label="ShowAll"/>
 </enum>
 
+<enum name="NTPHistoryClustersIneligibleReason">
+  <int value="0" label="None"/>
+  <int value="1" label="No available clusters"/>
+  <int value="2" label="Non prominent"/>
+  <int value="3" label="No SRP visit"/>
+  <int value="4" label="Insufficient visits"/>
+  <int value="5" label="Insufficient images"/>
+  <int value="6" label="Insufficient related searches"/>
+</enum>
+
 <enum name="NTPHistoryClustersModuleDisplayLayout">
   <int value="0" label="Not displayed"/>
   <int value="1" label="Layout 1"/>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 66942b9..3f27111 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -473,6 +473,26 @@
   </token>
 </histogram>
 
+<histogram name="Arc.AppInstall.Manual.NumAppsIncomplete" units="ms"
+    expires_after="2023-09-15">
+  <owner>batoon@google.com</owner>
+  <owner>arc-core@google.com</owner>
+  <summary>
+    Record the number of manual installs that did not complete. This metric is
+    recorded when the ARC session is stopped.
+  </summary>
+</histogram>
+
+<histogram name="Arc.AppInstall.Manual.TimeDelta" units="ms"
+    expires_after="2023-09-15">
+  <owner>batoon@google.com</owner>
+  <owner>arc-core@google.com</owner>
+  <summary>
+    Record the length of time it took to complete a manual install. This metric
+    is recorded when a manual install succeeds.
+  </summary>
+</histogram>
+
 <histogram name="Arc.AppInstall.PolicySuccessRate" units="%"
     expires_after="2023-08-08">
   <owner>batoon@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 3194fcc..d3da869 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2797,6 +2797,26 @@
   </summary>
 </histogram>
 
+<histogram name="Ash.EndOfLife.IncentiveButtonClicked"
+    enum="EolIncentiveButtonType" expires_after="2023-09-21">
+  <owner>mmourgos@chromium.org</owner>
+  <owner>tbarzic@chromium.org</owner>
+  <summary>
+    Tracks the number of times that each end of life incentive button has been
+    clicked. Recorded when the button is clicked to open the incentive url.
+  </summary>
+</histogram>
+
+<histogram name="Ash.EndOfLife.IncentiveShowSource"
+    enum="EolIncentiveShowSource" expires_after="2023-09-21">
+  <owner>mmourgos@chromium.org</owner>
+  <owner>tbarzic@chromium.org</owner>
+  <summary>
+    Tracks the number of times that each end of life incentive type is shown.
+    Recorded as soon as the end of life incentive is shown on the screen.
+  </summary>
+</histogram>
+
 <histogram name="Ash.EventLatency.Core.TotalLatency" units="microseconds"
     expires_after="2023-10-27">
   <owner>xiyuan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 82a80c8..90ea528b 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -356,6 +356,56 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.Camera.Effects.Error" enum="CameraEffectError"
+    expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the first error encountered in a camera which uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+</histogram>
+
+<histogram name="ChromeOS.Camera.Effects.NumConcurrentProcessedStreams"
+    units="count" expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the number streams that required rendering effects in a camera
+    session which uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+</histogram>
+
+<histogram name="ChromeOS.Camera.Effects.NumConcurrentStreams" units="count"
+    expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the number streams in a camera session which uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+</histogram>
+
+<histogram name="ChromeOS.Camera.Effects.NumStillShotsTaken" units="captures"
+    expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the number of still capture shots taken in a camera session which
+    uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+</histogram>
+
 <histogram name="ChromeOS.Camera.Effects.SelectedEffect"
     enum="CameraEffectType" expires_after="2024-01-01">
   <owner>jmpollock@chromium.org</owner>
@@ -428,6 +478,40 @@
   </token>
 </histogram>
 
+<histogram name="ChromeOS.Camera.Effects.{StreamType}.MaxStreamSize"
+    units="pixels" expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the number of pixels in one frame of the largest stream in a camera
+    session which uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+  <token key="StreamType">
+    <variant name="BLOB" summary="Maximum stream size for BLOB (JPEG) streams"/>
+    <variant name="YUV" summary="Maximum stream size for YUV streams"/>
+  </token>
+</histogram>
+
+<histogram name="ChromeOS.Camera.Effects.{StreamType}.MinStreamSize"
+    units="pixels" expires_after="2024-03-21">
+  <owner>skyostil@chromium.org</owner>
+  <owner>chromeos-camera-eng@google.com</owner>
+  <summary>
+    Records the number of pixels in one frame of the smallest stream in a camera
+    session which uses effects.
+
+    This metric is emitting once every hour if the camera session exceeds an
+    hour, and once at the end of every session.
+  </summary>
+  <token key="StreamType">
+    <variant name="BLOB" summary="Minimum stream size for BLOB (JPEG) streams"/>
+    <variant name="YUV" summary="Minimum stream size for YUV streams"/>
+  </token>
+</histogram>
+
 <histogram name="ChromeOS.Camera.ErrorType" enum="ChromeOSCameraErrorType"
     expires_after="2023-10-01">
   <owner>wtlee@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 854f657..f726a914 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -4825,15 +4825,6 @@
   <affected-histogram name="Net.QuicSession.Connect"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="QuicSessionVerifyProofTime" separator=".">
-  <suffix name="" label="Verification time for a server."/>
-  <suffix name="CachedServerConfig"
-      label="Verification time for a server when server config from cache is
-             used."/>
-  <suffix name="google" label="Verification time for www.google.com server."/>
-  <affected-histogram name="Net.QuicSession.VerifyProofTime"/>
-</histogram_suffixes>
-
 <histogram_suffixes name="RasterTaskSchedulingDelayNoAtRasterDecodesType"
     separator=".">
   <suffix name="All"
diff --git a/tools/metrics/histograms/metadata/holding_space/histograms.xml b/tools/metrics/histograms/metadata/holding_space/histograms.xml
index 4b39e35d..893486a1 100644
--- a/tools/metrics/histograms/metadata/holding_space/histograms.xml
+++ b/tools/metrics/histograms/metadata/holding_space/histograms.xml
@@ -39,6 +39,16 @@
 <variants name="HoldingSpaceItemType">
   <variant name="All" summary="Includes all item types."/>
   <variant name="ArcDownload" summary="Items backed by an ARC download file."/>
+  <variant name="CameraAppPhoto"
+      summary="Items backed by a camera app photo file."/>
+  <variant name="CameraAppScanJpg"
+      summary="Items backed by a JPG camera app scan file."/>
+  <variant name="CameraAppScanPdf"
+      summary="Items backed by a PDF camera app scan file."/>
+  <variant name="CameraAppVideoGif"
+      summary="Items backed by a GIF camera app video file."/>
+  <variant name="CameraAppVideoMp4"
+      summary="Items backed by a MP4 camera app video file."/>
   <variant name="DiagnosticsLog"
       summary="Items backed by a diagnostics log file."/>
   <variant name="Download"
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index 4f26b9e..9f627a4 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -2279,11 +2279,14 @@
 </histogram>
 
 <histogram name="Net.QuicActiveSessions" units="units"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The number of active QUIC sessions before we activate a new QUIC session.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -2518,12 +2521,15 @@
 </histogram>
 
 <histogram name="Net.QuicHandshakeNotConfirmedNumPacketsReceived" units="units"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The number of QUIC packets received by a QUIC connection whose handshake was
     not confirmed when that connection is closed.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -2710,12 +2716,15 @@
 </histogram>
 
 <histogram name="Net.QuicSession.AsyncRead" enum="Boolean"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     True if the result of reading a packet from the network was ERR_IO_PENDING.
     Recorded for each packet when Read() returns.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -2897,11 +2906,14 @@
 
 <histogram
     name="Net.QuicSession.ConnectionClose.NumOpenStreams.HandshakeTimedOut"
-    units="units" expires_after="2022-09-11">
+    units="units" expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The number of streams open when a QUIC session crypto handshake timed out.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -3018,12 +3030,15 @@
 
 <histogram
     name="Net.QuicSession.ConnectionCloseErrorCodeServerIetfTransportGoogle"
-    enum="QuicTransportErrorCodes" expires_after="2022-09-11">
+    enum="QuicTransportErrorCodes" expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The QUIC application transport error code in a CONNECTION_CLOSE frame
     received from a Google server.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -3204,13 +3219,16 @@
 </histogram>
 
 <histogram name="Net.QuicSession.FinchConfigIsValid" enum="Boolean"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     Logged every time we parse a QUIC Finch config, and logs whether the config
     was valid. Invalid configs are most likely to be obsolete configs. Note that
     this is still logged (as true) when no variations config is present.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -3308,29 +3326,38 @@
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioHpackReceived" units="%"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     Header compression ratio as percentage for received headers using HPACK.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioHpackSent" units="%"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     Header compression ratio as percentage for sent headers using HPACK.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
 <histogram name="Net.QuicSession.HeaderCompressionRatioQpackReceived" units="%"
-    expires_after="2022-09-11">
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     Header compression ratio as percentage for received headers using QPACK.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -3923,12 +3950,15 @@
 </histogram>
 
 <histogram name="Net.QuicSession.ReceivedSettings.MaxTableCapacity2"
-    units="bytes" expires_after="2022-09-11">
+    units="bytes" expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     The value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter received on an
     HTTP/3 connection, if any.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
 </histogram>
 
@@ -4291,15 +4321,26 @@
   </summary>
 </histogram>
 
-<histogram name="Net.QuicSession.VerifyProofTime" units="ms"
-    expires_after="2022-09-11">
+<histogram name="Net.QuicSession.VerifyProofTime{Type}" units="ms"
+    expires_after="2024-03-22">
   <owner>dschinazi@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
     Time spent verifying the signature and certificate chain. This is logged
     whenever QUIC verifies the certificate chain and signature during crypto
     handshake.
+
+    Warning: this histogram was expired from 09/12/2022 to 03/22/2023; data may
+    be missing.
   </summary>
+  <token key="Type">
+    <variant name="" summary="Verification time for a server."/>
+    <variant name=".CachedServerConfig"
+        summary="Verification time for a server when server config from cache
+                 is used."/>
+    <variant name=".google"
+        summary="Verification time for www.google.com server."/>
+  </token>
 </histogram>
 
 <histogram name="Net.QuicSession.WriteError" enum="NetErrorCodes"
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index c91c506..71b0735 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -995,6 +995,19 @@
   </summary>
 </histogram>
 
+<histogram name="NewTabPage.HistoryClusters.IneligibleReason"
+    enum="NTPHistoryClustersIneligibleReason" expires_after="2023-08-08">
+  <owner>romanarora@chromium.org</owner>
+  <owner>tiborg@chromium.org</owner>
+  <owner>chrome-desktop-ntp@google.com</owner>
+  <summary>
+    Logs the reason why the last candidate cluster of a list of of ineligible
+    candidate clusters was identified as such, resulting in the module not being
+    displayed. Logged when the NewTabPage is rendering the enabled modules. Only
+    logged on the 1P NTP and the history clusters module is enabled.
+  </summary>
+</histogram>
+
 <histogram name="NewTabPage.HistoryClusters.Layout{LayoutNum}.Click"
     enum="NTPHistoryClustersElementType" expires_after="2023-08-08">
   <owner>romanarora@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index 007d3f6..58a221b2 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -30,6 +30,15 @@
   <variant name="5000ms"/>
 </variants>
 
+<variants name="PrivacySandboxAdsApi">
+  <variant name="AttributionReporting"/>
+  <variant name="FencedFrames"/>
+  <variant name="Fledge"/>
+  <variant name="PrivateAggregation"/>
+  <variant name="SharedStorage"/>
+  <variant name="Topics"/>
+</variants>
+
 <variants name="RequestDestination">
   <variant name="audio"/>
   <variant name="audioworklet"/>
@@ -1099,6 +1108,58 @@
   <token key="PrerenderTriggerType" variants="PrerenderTriggerType"/>
 </histogram>
 
+<histogram
+    name="PageLoad.Clients.PrivacySandboxAds.InteractiveTiming.FirstInputDelay4.{PrivacySandboxAdsApi}"
+    units="ms" expires_after="M117">
+  <owner>linnan@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    PageLoad.InteractiveTiming.FirstInputDelay4, but recorded iff
+    {PrivacySandboxAdsApi} was used in the page.
+  </summary>
+  <token key="PrivacySandboxAdsApi" variants="PrivacySandboxAdsApi"/>
+</histogram>
+
+<histogram
+    name="PageLoad.Clients.PrivacySandboxAds.LayoutInstability.MaxCumulativeShiftScore.SessionWindow.Gap1000ms.Max5000ms2.{PrivacySandboxAdsApi}"
+    units="ms" expires_after="M117">
+  <owner>linnan@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    PageLoad.LayoutInstability.MaxCumulativeShiftScore.SessionWindow.Gap1000ms.Max5000ms2,
+    but recorded iff {PrivacySandboxAdsApi} was used in the page.
+  </summary>
+  <token key="PrivacySandboxAdsApi" variants="PrivacySandboxAdsApi"/>
+</histogram>
+
+<histogram
+    name="PageLoad.Clients.PrivacySandboxAds.PaintTiming.NavigationToFirstContentfulPaint.{PrivacySandboxAdsApi}"
+    units="ms" expires_after="M117">
+  <owner>linnan@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    PageLoad.PaintTiming.NavigationToFirstContentfulPaint, but recorded iff
+    {PrivacySandboxAdsApi} was used in the page.
+  </summary>
+  <token key="PrivacySandboxAdsApi" variants="PrivacySandboxAdsApi"/>
+</histogram>
+
+<histogram
+    name="PageLoad.Clients.PrivacySandboxAds.PaintTiming.NavigationToLargestContentfulPaint2.{PrivacySandboxAdsApi}"
+    units="ms" expires_after="M117">
+  <owner>linnan@chromium.org</owner>
+  <owner>johnidel@chromium.org</owner>
+  <owner>measurement-api-dev+metrics@google.com</owner>
+  <summary>
+    PageLoad.PaintTiming.NavigationToLargestContentfulPaint2, but recorded iff
+    {PrivacySandboxAdsApi} was used in the page.
+  </summary>
+  <token key="PrivacySandboxAdsApi" variants="PrivacySandboxAdsApi"/>
+</histogram>
+
 <histogram name="PageLoad.Clients.Scheme.HTTP.PaintTiming.UnderStat"
     enum="PageLoadTimingUnderStat" expires_after="2022-04-03">
   <owner>tbansal@chromium.org</owner>
diff --git a/tools/rust/package_rust.py b/tools/rust/package_rust.py
index 936f7c83..d70505a 100755
--- a/tools/rust/package_rust.py
+++ b/tools/rust/package_rust.py
@@ -14,8 +14,7 @@
 sys.path.append(
     os.path.join(os.path.dirname(THIS_DIR), '..', 'clang', 'scripts'))
 
-from build_rust import (RUST_TOOLCHAIN_OUT_DIR, THIRD_PARTY_DIR,
-                        VERSION_STAMP_PATH)
+from build_rust import (RUST_TOOLCHAIN_OUT_DIR, THIRD_PARTY_DIR)
 from update_rust import (GetPackageVersionForBuild)
 from package import (MaybeUpload, TeeCmd)
 from update import (CHROMIUM_DIR)
diff --git a/tools/rust/update_rust.py b/tools/rust/update_rust.py
index 14ab2a31..fa88428f 100755
--- a/tools/rust/update_rust.py
+++ b/tools/rust/update_rust.py
@@ -98,7 +98,7 @@
 
 # Get the version of the toolchain package we already have.
 def GetStampVersion():
-    if os.path.exists(RUST_TOOLCHAIN_OUT_DIR):
+    if os.path.exists(VERSION_STAMP_PATH):
         with open(VERSION_STAMP_PATH) as version_file:
             existing_stamp = version_file.readline().rstrip()
         version_re = re.compile(r'rustc [0-9.]+ [0-9a-f]+ \((.+?) chromium\)')
diff --git a/ui/chromeos/events/keyboard_capability.cc b/ui/chromeos/events/keyboard_capability.cc
index 69c3f539..65dbd3c6 100644
--- a/ui/chromeos/events/keyboard_capability.cc
+++ b/ui/chromeos/events/keyboard_capability.cc
@@ -167,6 +167,26 @@
 }
 
 // static
+bool KeyboardCapability::IsTopRowKey(const KeyboardCode& key_code) {
+  // A set that includes all top row keys from different keyboards.
+  static const base::NoDestructor<base::flat_set<KeyboardCode>>
+      top_row_action_keys({
+          KeyboardCode::VKEY_BROWSER_BACK,
+          KeyboardCode::VKEY_BROWSER_FORWARD,
+          KeyboardCode::VKEY_BROWSER_REFRESH,
+          KeyboardCode::VKEY_ZOOM,
+          KeyboardCode::VKEY_MEDIA_LAUNCH_APP1,
+          KeyboardCode::VKEY_BRIGHTNESS_DOWN,
+          KeyboardCode::VKEY_BRIGHTNESS_UP,
+          KeyboardCode::VKEY_MEDIA_PLAY_PAUSE,
+          KeyboardCode::VKEY_VOLUME_MUTE,
+          KeyboardCode::VKEY_VOLUME_DOWN,
+          KeyboardCode::VKEY_VOLUME_UP,
+      });
+  return base::Contains(*top_row_action_keys, key_code);
+}
+
+// static
 bool KeyboardCapability::HasSixPackKey(const InputDevice& keyboard) {
   // If the keyboard is an internal keyboard, return false. Otherwise, return
   // true. This is correct for most of the keyboards. Edge cases will be handled
@@ -298,4 +318,44 @@
   }
 }
 
+bool KeyboardCapability::HasKeyEvent(const KeyboardCode& key_code,
+                                     const InputDevice& keyboard) const {
+  // Handle top row keys.
+  if (IsTopRowKey(key_code)) {
+    KeyboardTopRowLayout layout =
+        EventRewriterChromeOS::GetKeyboardTopRowLayout(keyboard);
+    switch (layout) {
+      case KeyboardTopRowLayout::kKbdTopRowLayout1:
+        return kLayout1TopRowKeyToFKeyMap.contains(key_code);
+      case KeyboardTopRowLayout::kKbdTopRowLayout2:
+        return kLayout2TopRowKeyToFKeyMap.contains(key_code);
+      case KeyboardTopRowLayout::kKbdTopRowLayoutWilco:
+      case KeyboardTopRowLayout::kKbdTopRowLayoutDrallion:
+        return kLayoutWilcoDrallionTopRowKeyToFKeyMap.contains(key_code);
+      case KeyboardTopRowLayout::kKbdTopRowLayoutCustom:
+        // TODO(zhangwenyu): Handle custom vivaldi layout.
+        return true;
+    }
+  }
+
+  // Handle six pack keys.
+  if (IsSixPackKey(key_code)) {
+    return HasSixPackKey(keyboard);
+  }
+
+  // TODO(zhangwenyu): check other specific keys, e.g. assistant key.
+  return true;
+}
+
+bool KeyboardCapability::HasKeyEventOnAnyKeyboard(
+    const KeyboardCode& key_code) const {
+  for (const ui::InputDevice& keyboard :
+       ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
+    if (HasKeyEvent(key_code, keyboard)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace ui
diff --git a/ui/chromeos/events/keyboard_capability.h b/ui/chromeos/events/keyboard_capability.h
index a589a71..e9b0480a 100644
--- a/ui/chromeos/events/keyboard_capability.h
+++ b/ui/chromeos/events/keyboard_capability.h
@@ -177,6 +177,9 @@
   // Enable or disable top row keys as F-Keys.
   void SetTopRowKeysAsFKeysEnabledForTesting(bool enabled) const;
 
+  // Check if a key code is one of the top row keys.
+  static bool IsTopRowKey(const KeyboardCode& key_code);
+
   // Check if a key code is one of the six pack keys.
   static bool IsSixPackKey(const KeyboardCode& key_code);
 
@@ -217,6 +220,13 @@
   void OnDeviceListsComplete() override;
   void OnInputDeviceConfigurationChanged(uint8_t input_device_types) override;
 
+  // Check if a specific key event exists on a given keyboard.
+  bool HasKeyEvent(const KeyboardCode& key_code,
+                   const InputDevice& keyboard) const;
+
+  // Check if any of the connected keyboards has a specific key event.
+  bool HasKeyEventOnAnyKeyboard(const KeyboardCode& key_code) const;
+
   const base::flat_map<int, KeyboardInfo>& keyboard_info_map() {
     return keyboard_info_map_;
   }
diff --git a/ui/display/mac/display_link_mac.cc b/ui/display/mac/display_link_mac.cc
index 45657313..2ab7e0fe 100644
--- a/ui/display/mac/display_link_mac.cc
+++ b/ui/display/mac/display_link_mac.cc
@@ -11,6 +11,7 @@
 #include "base/logging.h"
 #include "base/no_destructor.h"
 #include "base/numerics/safe_conversions.h"
+#include "base/synchronization/lock.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/trace_event/trace_event.h"
@@ -30,6 +31,8 @@
 
 namespace ui {
 
+using DisplayLinkMap = std::map<CGDirectDisplayID, DisplayLinkMac*>;
+
 namespace {
 
 bool ComputeVSyncParameters(const CVTimeStamp& cv_time,
@@ -61,54 +64,37 @@
   return true;
 }
 
-}  // namespace
+struct DisplayLinkGlobals {
+  std::map<CGDirectDisplayID, DisplayLinkMac*> GUARDED_BY(lock) map;
+  base::Lock lock;
 
-using DisplayLinkMap = std::map<CGDirectDisplayID, DisplayLinkMac*>;
-
-namespace {
-
-// The task runner to post tasks to from the display link thread. Note that this
-// is initialized with the very first DisplayLinkMac instance, and is never
-// changed (even, e.g, in tests that re-initialize the main thread task runner).
-// https://885329
-// TODO(ccameron): crbug.com/969157 - Save this ask_runner to DisplayLinkMac.
-// configs += [ "//build/config/compiler:wexit_time_destructors" ] in
-// ui/display/BUILD.gn has to be removed because GetMainThreadTaskRunner()
-// causes a compiler error.
-scoped_refptr<base::SingleThreadTaskRunner> GetMainThreadTaskRunner() {
-  static scoped_refptr<base::SingleThreadTaskRunner> task_runner =
-      base::SingleThreadTaskRunner::GetCurrentDefault();
-  return task_runner;
-}
-
-// Each display link instance consumes a non-negligible number of cycles, so
-// make all display links on the same screen share the same object.
-//
-// Note that this is a weak map, holding non-owning pointers to the
-// DisplayLinkMac objects. DisplayLinkMac is a ref-counted class, and is
-// jointly owned by the various callers that got a copy by calling
-// GetForDisplay().
-//
-// ** This map may only be accessed from the main thread. **
-DisplayLinkMap& GetAllDisplayLinks() {
-  static base::NoDestructor<DisplayLinkMap> all_display_links;
-  return *all_display_links;
-}
+  static DisplayLinkGlobals& Get() {
+    static base::NoDestructor<DisplayLinkGlobals> instance;
+    return *instance;
+  }
+};
 
 }  // namespace
 
+////////////////////////////////////////////////////////////////////////////////
+// DisplayLinkMac
+
 // static
 scoped_refptr<DisplayLinkMac> DisplayLinkMac::GetForDisplay(
     CGDirectDisplayID display_id) {
   if (!display_id)
     return nullptr;
 
-  // Return the existing display link for this display, if it exists.
-  DisplayLinkMap& all_display_links = GetAllDisplayLinks();
-  auto found = all_display_links.find(display_id);
-  if (found != all_display_links.end())
-    return found->second;
+  auto& globals = DisplayLinkGlobals::Get();
+  base::AutoLock lock(globals.lock);
 
+  // Return the existing display link for this display, if it exists.
+  auto found = globals.map.find(display_id);
+  if (found != globals.map.end()) {
+    return found->second;
+  }
+
+  // Create a new DisplayLink, outside of the lock.
   CVReturn ret = kCVReturnSuccess;
 
   base::ScopedTypeRef<CVDisplayLinkRef> display_link;
@@ -137,17 +123,17 @@
     return nullptr;
   }
 
-  scoped_refptr<DisplayLinkMac> display_link_mac(
-      new DisplayLinkMac(display_id, display_link));
-  ret = CVDisplayLinkSetOutputCallback(display_link_mac->display_link_,
-                                       &DisplayLinkCallback,
+  ret = CVDisplayLinkSetOutputCallback(display_link, &DisplayLinkCallback,
                                        reinterpret_cast<void*>(display_id));
   if (ret != kCVReturnSuccess) {
     LOG(ERROR) << "CVDisplayLinkSetOutputCallback failed: " << ret;
     return nullptr;
   }
 
-  return display_link_mac;
+  scoped_refptr<DisplayLinkMac> result(
+      new DisplayLinkMac(display_id, display_link));
+  globals.map.emplace(display_id, result.get());
+  return result;
 }
 
 double DisplayLinkMac::GetRefreshRate() {
@@ -164,49 +150,18 @@
 DisplayLinkMac::DisplayLinkMac(
     CGDirectDisplayID display_id,
     base::ScopedTypeRef<CVDisplayLinkRef> display_link)
-    : display_id_(display_id), display_link_(display_link) {
-  DisplayLinkMap& all_display_links = GetAllDisplayLinks();
-  DCHECK(all_display_links.find(display_id) == all_display_links.end());
-  all_display_links.emplace(display_id_, this);
-}
+    : display_id_(display_id), display_link_(display_link) {}
 
 DisplayLinkMac::~DisplayLinkMac() {
+  auto& globals = DisplayLinkGlobals::Get();
+  base::AutoLock lock(globals.lock);
+
   DCHECK(callbacks_.empty());
 
-  DisplayLinkMap& all_display_links = GetAllDisplayLinks();
-  auto found = all_display_links.find(display_id_);
-  DCHECK(found != all_display_links.end());
+  auto found = globals.map.find(display_id_);
+  DCHECK(found != globals.map.end());
   DCHECK(found->second == this);
-  all_display_links.erase(found);
-}
-
-// static
-void DisplayLinkMac::DisplayLinkCallbackOnMainThread(CGDirectDisplayID display,
-                                                     VSyncParamsMac params) {
-  DisplayLinkMap& all_display_links = GetAllDisplayLinks();
-  auto found = all_display_links.find(display);
-  if (found == all_display_links.end()) {
-    // This might reasonably happen (and does; see https://crbug.com/564780). It
-    // occasionally happens that the CVDisplayLink calls back on the video
-    // thread, but by the time the callback makes it to the main thread for
-    // processing, the DisplayLinkMac object has lost all its references and
-    // has been deleted.
-    return;
-  }
-
-  DisplayLinkMac* display_link_mac = found->second;
-  display_link_mac->OnDisplayLinkCallback(params);
-}
-
-void DisplayLinkMac::OnDisplayLinkCallback(VSyncParamsMac params) {
-  TRACE_EVENT0("ui", "DisplayLinkMac::OnDisplayLinkCallbackOnMainThread");
-
-  auto callbacks_copy = callbacks_;
-  for (auto* callback : callbacks_copy) {
-    if (callbacks_.count(callback)) {
-      callback->callback_.Run(params);
-    }
-  }
+  globals.map.erase(found);
 }
 
 // static
@@ -217,33 +172,41 @@
                                              CVOptionFlags* flags_out,
                                              void* context) {
   TRACE_EVENT0("ui", "DisplayLinkMac::DisplayLinkCallback");
-  CGDirectDisplayID display_id =
-      static_cast<CGDirectDisplayID>(reinterpret_cast<uintptr_t>(context));
 
+  // Convert the time parameters to our VSync parameters.
   VSyncParamsMac params;
   params.callback_times_valid = ComputeVSyncParameters(
       *now, &params.callback_timebase, &params.callback_interval);
   params.display_times_valid = ComputeVSyncParameters(
       *output_time, &params.display_timebase, &params.display_interval);
 
-  GetMainThreadTaskRunner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&DisplayLinkMac::DisplayLinkCallbackOnMainThread,
-                     display_id, params));
+  // Locate the DisplayLinkMac for this display.
+  auto& globals = DisplayLinkGlobals::Get();
+  base::AutoLock lock(globals.lock);
+  CGDirectDisplayID display_id =
+      static_cast<CGDirectDisplayID>(reinterpret_cast<uintptr_t>(context));
+  auto found = globals.map.find(display_id);
+  if (found == globals.map.end()) {
+    return kCVReturnSuccess;
+  }
+
+  // Issue all of its callbacks.
+  DisplayLinkMac* display_link_mac = found->second;
+  for (auto* callback : display_link_mac->callbacks_) {
+    callback->callback_for_cvdisplaylink_thread_.Run(params);
+  }
   return kCVReturnSuccess;
 }
 
 std::unique_ptr<VSyncCallbackMac> DisplayLinkMac::RegisterCallback(
-    VSyncCallbackMac::Callback callback) {
+    VSyncCallbackMac::Callback callback,
+    bool do_callback_on_register_thread) {
+  base::AutoLock lock(DisplayLinkGlobals::Get().lock);
+
   // Start the display link, if needed. If we fail to start the link, return
   // nullptr.
   if (callbacks_.empty()) {
     DCHECK(!CVDisplayLinkIsRunning(display_link_));
-
-    if (!task_runner_) {
-      task_runner_ = GetMainThreadTaskRunner();
-    }
-
     CVReturn ret = CVDisplayLinkStart(display_link_);
     if (ret != kCVReturnSuccess) {
       LOG(ERROR) << "CVDisplayLinkStart failed: " << ret;
@@ -251,13 +214,15 @@
     }
   }
 
-  std::unique_ptr<VSyncCallbackMac> new_observer(
-      new VSyncCallbackMac(this, std::move(callback)));
+  std::unique_ptr<VSyncCallbackMac> new_observer(new VSyncCallbackMac(
+      this, std::move(callback), do_callback_on_register_thread));
   callbacks_.insert(new_observer.get());
   return new_observer;
 }
 
 void DisplayLinkMac::UnregisterCallback(VSyncCallbackMac* observer) {
+  base::AutoLock lock(DisplayLinkGlobals::Get().lock);
+
   auto found = callbacks_.find(observer);
   CHECK(found != callbacks_.end());
   callbacks_.erase(found);
@@ -276,8 +241,24 @@
 // VSyncCallbackMac
 
 VSyncCallbackMac::VSyncCallbackMac(scoped_refptr<DisplayLinkMac> display_link,
-                                   Callback callback)
-    : display_link_(std::move(display_link)), callback_(std::move(callback)) {}
+                                   Callback callback,
+                                   bool do_callback_on_ctor_thread)
+    : display_link_(std::move(display_link)), weak_factory_(this) {
+  if (do_callback_on_ctor_thread) {
+    auto lambda = [](base::WeakPtr<VSyncCallbackMac> weak_this,
+                     Callback callback, VSyncParamsMac params) {
+      if (weak_this) {
+        callback.Run(params);
+      }
+    };
+    auto callback_for_current_thread =
+        base::BindRepeating(lambda, weak_factory_.GetWeakPtr(), callback);
+    callback_for_cvdisplaylink_thread_ =
+        base::BindPostTaskToCurrentDefault(callback_for_current_thread);
+  } else {
+    callback_for_cvdisplaylink_thread_ = std::move(callback);
+  }
+}
 
 VSyncCallbackMac::~VSyncCallbackMac() {
   display_link_->UnregisterCallback(this);
diff --git a/ui/display/mac/display_link_mac.h b/ui/display/mac/display_link_mac.h
index 5355ff0c..ce3e281f 100644
--- a/ui/display/mac/display_link_mac.h
+++ b/ui/display/mac/display_link_mac.h
@@ -36,6 +36,7 @@
 };
 
 // Object used to control the lifetime of callbacks from DisplayLinkMac.
+// See notes in DisplayLinkMac::RegisterCallback
 class DISPLAY_EXPORT VSyncCallbackMac {
  public:
   using Callback = base::RepeatingCallback<void(VSyncParamsMac)>;
@@ -44,28 +45,43 @@
  private:
   friend class DisplayLinkMac;
   VSyncCallbackMac(scoped_refptr<DisplayLinkMac> display_link,
-                   Callback callback);
+                   Callback callback,
+                   bool do_callback_on_ctor_thread);
 
   // The DisplayLinkMac that `this` is observing is kept alive while `this` is
   // alive.
   scoped_refptr<DisplayLinkMac> display_link_;
-  Callback callback_;
+
+  // The callback that will be run on the CVDisplayLink thread. If `this` was
+  // created with `do_callback_on_ctor_thread`, then this callback will post a
+  // task to the creating thread,
+  Callback callback_for_cvdisplaylink_thread_;
+
+  base::WeakPtrFactory<VSyncCallbackMac> weak_factory_;
 };
 
 class DISPLAY_EXPORT DisplayLinkMac
     : public base::RefCountedThreadSafe<DisplayLinkMac> {
  public:
-  // Get the DisplayLinkMac for the specified display. All calls to this
-  // function (and all other functions on this class) must be on the same
-  // thread.
-  // TODO(https://crbug.com/1419870): Remove this restriction.
+  // Get the DisplayLinkMac for the specified display. This may be called on
+  // any thread.
   static scoped_refptr<DisplayLinkMac> GetForDisplay(
       CGDirectDisplayID display_id);
 
-  // Register an observer callback. The specified callback will be called at
-  // every VSync tick until the returned object is destroyed.
+  // Register an observer callback.
+  // * The specified callback will be called at every VSync tick, until the
+  //   returned VSyncCallbackMac object is destroyed.
+  // * The resulting VSyncCallbackMac object must be destroyed on the same
+  //   thread on which it was created.
+  // * If `do_callback_on_register_thread` is true, then the callback is
+  //   guaranteed to be made on the calling thread and is guaranteed to be made
+  //   only if the resulting VSyncCallbackMac has not been destroyed.
+  // * If `do_callback_on_register_thread` is false then the callback may come
+  //   from any thread, and may happen after the resulting VSyncCallbackMac is
+  //   destroyed.
   std::unique_ptr<VSyncCallbackMac> RegisterCallback(
-      VSyncCallbackMac::Callback callback);
+      VSyncCallbackMac::Callback callback,
+      bool do_callback_on_register_thread = true);
 
   // Get the panel/monitor refresh rate
   double GetRefreshRate();
@@ -78,7 +94,6 @@
                  base::ScopedTypeRef<CVDisplayLinkRef> display_link);
   virtual ~DisplayLinkMac();
 
-  void OnDisplayLinkCallback(VSyncParamsMac params);
   void UnregisterCallback(VSyncCallbackMac* callback);
 
   // Called by the system on the display link thread, and posts a call to
@@ -90,21 +105,14 @@
                                       CVOptionFlags* flags_out,
                                       void* context);
 
-  // Looks up the display and calls UpdateVSyncParameters() on the corresponding
-  // DisplayLinkMac.
-  static void DisplayLinkCallbackOnMainThread(CGDirectDisplayID display,
-                                              VSyncParamsMac params);
-
   // The display that this display link is attached to.
   CGDirectDisplayID display_id_;
 
   // CVDisplayLink for querying VSync timing info.
   base::ScopedTypeRef<CVDisplayLinkRef> display_link_;
 
-  // The task runner to post tasks to from the display link thread.
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-
-  // Each VSyncCallbackMac holds a reference to `this`.
+  // Each VSyncCallbackMac holds a reference to `this`. This member may only
+  // be accessed while DisplayLinkGlobals::lock is held.
   std::set<VSyncCallbackMac*> callbacks_;
 };
 
diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc
index 1ad57d5a..9ad01f1 100644
--- a/ui/events/blink/blink_event_util.cc
+++ b/ui/events/blink/blink_event_util.cc
@@ -180,6 +180,7 @@
                             event.GetY(pointer_index));
   touch.SetPositionInScreen(event.GetRawX(pointer_index),
                             event.GetRawY(pointer_index));
+  touch.device_id = event.GetSourceDeviceId(pointer_index);
 
   // A note on touch ellipse specifications:
   //
diff --git a/ui/events/blink/web_input_event.cc b/ui/events/blink/web_input_event.cc
index d2cfb3c8e..95587f8c 100644
--- a/ui/events/blink/web_input_event.cc
+++ b/ui/events/blink/web_input_event.cc
@@ -433,6 +433,7 @@
   webkit_event.twist = event.pointer_details().twist;
   webkit_event.id = event.pointer_details().id;
   webkit_event.pointer_type = event.pointer_details().pointer_type;
+  webkit_event.device_id = event.source_device_id();
 
   return webkit_event;
 }
diff --git a/ui/events/gesture_detection/motion_event_generic.cc b/ui/events/gesture_detection/motion_event_generic.cc
index a466469..d277dff 100644
--- a/ui/events/gesture_detection/motion_event_generic.cc
+++ b/ui/events/gesture_detection/motion_event_generic.cc
@@ -249,6 +249,11 @@
   return historical_events_[historical_index]->GetY(pointer_index);
 }
 
+int32_t MotionEventGeneric::GetSourceDeviceId(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].source_device_id;
+}
+
 // static
 std::unique_ptr<MotionEventGeneric> MotionEventGeneric::CloneEvent(
     const MotionEvent& event) {
diff --git a/ui/events/gesture_detection/motion_event_generic.h b/ui/events/gesture_detection/motion_event_generic.h
index de9ee23..2e5f433f 100644
--- a/ui/events/gesture_detection/motion_event_generic.h
+++ b/ui/events/gesture_detection/motion_event_generic.h
@@ -88,6 +88,8 @@
   float GetHistoricalY(size_t pointer_index,
                        size_t historical_index) const override;
 
+  int32_t GetSourceDeviceId(size_t pointer_index) const override;
+
   // Adds |pointer| to the set of pointers returning the index it was added at.
   size_t PushPointer(const PointerProperties& pointer);
 
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
index c628789..dd5988e 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model.js
@@ -752,18 +752,6 @@
       this.fakeDriveItem_.section = NavigationSection.CLOUD;
     }
 
-    // Add Trash.
-    // This should only show when Files app is open as a standalone app. The ARC
-    // file selector, however, opens Files app as a standalone app but passes a
-    // query parameter to indicate the mode. As Trash is a fake volume, it is
-    // not filtered out in the filtered volume manager so perform it here
-    // instead.
-    if (util.isTrashEnabled() && this.dialogType_ === DialogType.FULL_PAGE &&
-        !this.volumeManager_.getMediaStoreFilesOnlyFilterEnabled() &&
-        this.trashItem_) {
-      this.navigationItems_.push(this.trashItem_);
-    }
-
     // Add SMB.
     for (const provided of getVolumes(VolumeManagerCommon.VolumeType.SMB)) {
       this.navigationItems_.push(provided);
@@ -862,6 +850,18 @@
       androidAppItem.section = NavigationSection.ANDROID_APPS;
       this.navigationItems_.push(androidAppItem);
     }
+
+    // Add Trash.
+    // This should only show when Files app is open as a standalone app. The ARC
+    // file selector, however, opens Files app as a standalone app but passes a
+    // query parameter to indicate the mode. As Trash is a fake volume, it is
+    // not filtered out in the filtered volume manager so perform it here
+    // instead.
+    if (util.isTrashEnabled() && this.dialogType_ === DialogType.FULL_PAGE &&
+        !this.volumeManager_.getMediaStoreFilesOnlyFilterEnabled() &&
+        this.trashItem_) {
+      this.navigationItems_.push(this.trashItem_);
+    }
   }
 
   /**
diff --git a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
index 3c7a8c9..577c9a77 100644
--- a/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
+++ b/ui/file_manager/file_manager/foreground/js/navigation_list_model_unittest.js
@@ -123,7 +123,8 @@
   assertEquals(1, myFilesEntryList.getUIChildren().length);
   assertEquals('linux-files-label', myFilesEntryList.getUIChildren()[0].name);
 
-  // Trash is displayed as a root when feature is enabled.
+  // Trash is displayed as a root when feature is enabled and should be the last
+  // item in the model.
   loadTimeData.overrideValues({FILES_TRASH_ENABLED: true});
   model.fakeTrashItem = new NavigationModelFakeItem(
       'trash-label', NavigationModelItemType.TRASH, new TrashRootEntry());
@@ -131,7 +132,7 @@
   assertEquals(7, model.length);
   assertEquals(
       'fake-entry://trash', /** @type {!NavigationModelFakeItem} */
-      (model.item(4)).entry.toURL());
+      (model.item(6)).entry.toURL());
 }
 
 /**
diff --git a/ui/file_manager/file_manager/state/reducers/navigation.ts b/ui/file_manager/file_manager/state/reducers/navigation.ts
index d0f77be..d46b8f2 100644
--- a/ui/file_manager/file_manager/state/reducers/navigation.ts
+++ b/ui/file_manager/file_manager/state/reducers/navigation.ts
@@ -49,10 +49,10 @@
  *  2. Shortcuts.
  *  3. "My-Files" (grouping), actually Downloads volume.
  *  4. Drive volumes.
- *  5. Trash.
- *  6. Other FSP (File System Provider) (when mounted).
- *  7. Other volumes (MTP, ARCHIVE, REMOVABLE).
- *  8. Android apps.
+ *  5. Other FSP (File System Provider) (when mounted).
+ *  6. Other volumes (MTP, ARCHIVE, REMOVABLE).
+ *  7. Android apps.
+ *  8. Trash.
  */
 export function refreshNavigationRoots(
     currentState: State, _action: RefreshNavigationRootsAction): State {
@@ -127,20 +127,7 @@
     processedEntryKeys.add(driveEntry.toURL());
   }
 
-  // 5. Trash
-  const trashEntry =
-      getEntry(currentState, trashRootKey) as FilesAppEntry | null;
-  if (trashEntry) {
-    roots.push({
-      key: trashRootKey,
-      section: NavigationSection.TRASH,
-      separator: true,
-      type: NavigationType.TRASH,
-    });
-    processedEntryKeys.add(trashRootKey);
-  }
-
-  // 6/7. Other volumes.
+  // 5/6. Other volumes.
   const volumesOrder = {
     [VolumeType.SMB]: 1,
     [VolumeType.PROVIDED]: 2,  // FSP.
@@ -200,7 +187,7 @@
     }
   }
 
-  // 8. Android Apps.
+  // 7. Android Apps.
   Object
       .values(
           androidApps as Record<string, chrome.fileManagerPrivate.AndroidApp>)
@@ -214,6 +201,19 @@
         processedEntryKeys.add(app.packageName);
       });
 
+  // 8. Trash
+  const trashEntry =
+      getEntry(currentState, trashRootKey) as FilesAppEntry | null;
+  if (trashEntry) {
+    roots.push({
+      key: trashRootKey,
+      section: NavigationSection.TRASH,
+      separator: true,
+      type: NavigationType.TRASH,
+    });
+    processedEntryKeys.add(trashRootKey);
+  }
+
   return {
     ...currentState,
     navigation: {
diff --git a/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts b/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts
index 40658f2..9e4b81aa 100644
--- a/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts
+++ b/ui/file_manager/file_manager/state/reducers/navigation_unittest.ts
@@ -208,18 +208,19 @@
   //      * Android files - won't be included as root because it's inside
   //      MyFiles.
   //  5.  Drive
-  //  6.  Trash
-  //  7.  smb:file-share
-  //  8.  provided:prov1
-  //  9.  provided:prov2
+  //  6.  smb:file-share
+  //  7.  provided:prov1
+  //  8.  provided:prov2
   //
-  // 10.  removable:hoge
-  // 11.  removable:fuga
-  // 12.  archive:a-rar  - mounted as archive
-  // 13.  mtp:a-phone
+  //  9.  removable:hoge
+  // 10.  removable:fuga
+  // 11.  archive:a-rar  - mounted as archive
+  // 12.  mtp:a-phone
   //
-  // 14.  android:app1
-  // 15.  android:app2
+  // 13.  android:app1
+  // 14.  android:app2
+  //
+  // 15.  Trash
 
   // Check items order and that MTP/Archive/Removable respect the original
   // order.
@@ -259,13 +260,6 @@
       separator: true,
       type: NavigationType.DRIVE,
     },
-    // Trash.
-    {
-      key: trashEntryFileData.entry.toURL(),
-      section: NavigationSection.TRASH,
-      separator: true,
-      type: NavigationType.TRASH,
-    },
     // FSP, and SMB are grouped together.
     // smb:file-share.
     {
@@ -331,6 +325,13 @@
       separator: false,
       type: NavigationType.ANDROID_APPS,
     },
+    // Trash.
+    {
+      key: trashEntryFileData.entry.toURL(),
+      section: NavigationSection.TRASH,
+      separator: true,
+      type: NavigationType.TRASH,
+    },
   ];
   await waitDeepEquals(store, want, (state) => state.navigation.roots);
 
diff --git a/ui/gl/dc_layer_tree.cc b/ui/gl/dc_layer_tree.cc
index 92de18e6..52af882a5 100644
--- a/ui/gl/dc_layer_tree.cc
+++ b/ui/gl/dc_layer_tree.cc
@@ -280,13 +280,7 @@
     dcomp_visual_content_ = std::move(dcomp_visual_content);
     needs_commit = true;
     hr = content_visual_->SetContent(dcomp_visual_content_.Get());
-    if (FAILED(hr)) {
-      // This can be changed back to a CHECK_EQ once
-      // DirectCompositionPixelTest.RootSurfaceDrawOffset in
-      // ui/gl/direct_composition_surface_win_unittest.cc is removed.
-      DLOG(ERROR) << "SetContent failed: "
-                  << logging::SystemErrorCodeToString(hr);
-    }
+    CHECK_EQ(hr, S_OK);
   }
 
   if (dcomp_surface_serial_ != dcomp_surface_serial) {
diff --git a/ui/gl/direct_composition_surface_win_unittest.cc b/ui/gl/direct_composition_surface_win_unittest.cc
index e4a1f18a..792da262 100644
--- a/ui/gl/direct_composition_surface_win_unittest.cc
+++ b/ui/gl/direct_composition_surface_win_unittest.cc
@@ -1350,10 +1350,22 @@
 
   constexpr gfx::Point draw_offset(50, 50);
   auto root_surface = surface_->GetRootSurfaceForTesting();
+  Microsoft::WRL::ComPtr<IDCompositionSurface> orig_dcomp_surface =
+      root_surface->dcomp_surface();
   auto dcomp_surface =
       Microsoft::WRL::Make<DrawOffsetOverridingDCompositionSurface>(
           root_surface->dcomp_surface(), draw_offset);
-  root_surface->SetDCompSurfaceForTesting(std::move(dcomp_surface));
+
+  // Although |dcomp_surface| is not used beyond this point, it should not be
+  // freed. This is because surface_->SetDrawRectangle sets |dcomp_surface|
+  // as a row pointer to DirectCompositionChildSurfaceWin::g_current_surface,
+  // and later, root_surface->SetDCompSurfaceForTesting releases dcomp_surface
+  // without nullifying DirectCompositionChildSurfaceWin::g_current_surface.
+  // As a result, accessing the invalid
+  // DirectCompositionChildSurfaceWin::g_current_surface in
+  // DirectCompositionChildSurfaceWin::OnMakeCurrent leads to
+  // EXCEPTION_ACCESS_VIOLATION.
+  root_surface->SetDCompSurfaceForTesting(dcomp_surface);
 
   // Even though draw_rect is the first quadrant, the rendering will be limited
   // to the third quadrant because the dcomp surface will return that offset.
@@ -1363,6 +1375,10 @@
   glClearColor(1.0, 0.0, 0.0, 1.0);
   glClear(GL_COLOR_BUFFER_BIT);
 
+  // Reset the original dcomp surface so it can be successfully set to a visual
+  // in dcomp tree. Passing DrawOffsetOverridingDCompositionSurface to
+  // Visual::SetContent returns an error.
+  root_surface->SetDCompSurfaceForTesting(std::move(orig_dcomp_surface));
   EXPECT_EQ(gfx::SwapResult::SWAP_ACK,
             surface_->SwapBuffers(base::DoNothing(), gfx::FrameData()));
 
diff --git a/ui/gl/init/gl_display_initializer.cc b/ui/gl/init/gl_display_initializer.cc
index fb368d05..62617f6a 100644
--- a/ui/gl/init/gl_display_initializer.cc
+++ b/ui/gl/init/gl_display_initializer.cc
@@ -45,14 +45,16 @@
 
   // If we're already requesting software GL, make sure we don't fallback to the
   // GPU
-  bool forceSoftwareGL = IsSoftwareGLImplementation(GetGLImplementationParts());
+  bool force_software_gl =
+      IsSoftwareGLImplementation(GetGLImplementationParts());
 
   std::string requested_renderer =
-      forceSoftwareGL ? kANGLEImplementationSwiftShaderName
-                      : command_line->GetSwitchValueASCII(switches::kUseANGLE);
+      force_software_gl
+          ? kANGLEImplementationSwiftShaderName
+          : command_line->GetSwitchValueASCII(switches::kUseANGLE);
 
   bool use_angle_default =
-      !forceSoftwareGL &&
+      !force_software_gl &&
       (!command_line->HasSwitch(switches::kUseANGLE) ||
        requested_renderer == kANGLEImplementationDefaultName);
 
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index fc60060..14b9424 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -684,6 +684,7 @@
       "win/hwnd_message_handler_delegate.h",
       "win/hwnd_util.h",
       "win/pen_event_processor.h",
+      "win/pen_id_handler.h",
       "win/scoped_enable_unadjusted_mouse_events_win.h",
       "win/scoped_fullscreen_visibility.h",
     ]
@@ -695,6 +696,7 @@
       "win/hwnd_message_handler.cc",
       "win/hwnd_util_aurawin.cc",
       "win/pen_event_processor.cc",
+      "win/pen_id_handler.cc",
       "win/scoped_enable_unadjusted_mouse_events_win.cc",
       "win/scoped_fullscreen_visibility.cc",
     ]
@@ -1087,6 +1089,10 @@
     sources += [
       "test/desktop_window_tree_host_win_test_api.cc",
       "test/desktop_window_tree_host_win_test_api.h",
+      "win/test_support/fake_ipen_device.cc",
+      "win/test_support/fake_ipen_device.h",
+      "win/test_support/fake_ipen_device_statics.cc",
+      "win/test_support/fake_ipen_device_statics.h",
     ]
   }
   if (is_mac) {
@@ -1338,6 +1344,7 @@
       "accessibility/test_list_grid_view.h",
       "accessibility/view_ax_platform_node_delegate_win_unittest.cc",
       "win/pen_event_processor_unittest.cc",
+      "win/pen_id_handler_unittest.cc",
     ]
   }
 
diff --git a/ui/views/win/pen_event_processor.cc b/ui/views/win/pen_event_processor.cc
index dd0fa6b..7813863 100644
--- a/ui/views/win/pen_event_processor.cc
+++ b/ui/views/win/pen_event_processor.cc
@@ -7,6 +7,7 @@
 #include "base/check.h"
 #include "base/notreached.h"
 #include "base/time/time.h"
+#include "ui/events/event_constants.h"
 #include "ui/events/event_utils.h"
 
 namespace views {
@@ -68,10 +69,13 @@
       input_type, mapped_pointer_id, /* radius_x */ 0.0f, /* radius_y */ 0.0f,
       pressure, rotation_angle, tilt_x, tilt_y, /* tangential_pressure */ 0.0f);
 
+  int32_t device_id = pen_id_handler_.TryGetPenUniqueId(pointer_id)
+                          .value_or(ui::ED_UNKNOWN_DEVICE);
+
   // If the flag is disabled, we send mouse events for all pen inputs.
   if (!direct_manipulation_enabled_) {
     return GenerateMouseEvent(message, pointer_id, pointer_pen_info.pointerInfo,
-                              point, pointer_details);
+                              point, pointer_details, device_id);
   }
   bool is_pointer_event =
       message == WM_POINTERENTER || message == WM_POINTERLEAVE;
@@ -99,11 +103,11 @@
 
   if (is_pointer_event || !send_touch) {
     return GenerateMouseEvent(message, pointer_id, pointer_pen_info.pointerInfo,
-                              point, pointer_details);
+                              point, pointer_details, device_id);
   }
 
   return GenerateTouchEvent(message, pointer_id, pointer_pen_info.pointerInfo,
-                            point, pointer_details);
+                            point, pointer_details, device_id);
 }
 
 std::unique_ptr<ui::Event> PenEventProcessor::GenerateMouseEvent(
@@ -111,7 +115,8 @@
     UINT32 pointer_id,
     const POINTER_INFO& pointer_info,
     const gfx::Point& point,
-    const ui::PointerDetails& pointer_details) {
+    const ui::PointerDetails& pointer_details,
+    int32_t device_id) {
   ui::EventType event_type = ui::ET_MOUSE_MOVED;
   int flag = GetFlagsFromPointerMessage(message, pointer_info);
   int changed_flag = ui::EF_NONE;
@@ -164,6 +169,7 @@
       event_type, point, point, ui::EventTimeForNow(),
       flag | ui::GetModifiersFromKeyState(), changed_flag, pointer_details);
   event->AsMouseEvent()->SetClickCount(click_count);
+  event->set_source_device_id(device_id);
   return event;
 }
 
@@ -172,7 +178,8 @@
     UINT32 pointer_id,
     const POINTER_INFO& pointer_info,
     const gfx::Point& point,
-    const ui::PointerDetails& pointer_details) {
+    const ui::PointerDetails& pointer_details,
+    int32_t device_id) {
   int flags = GetFlagsFromPointerMessage(message, pointer_info);
 
   ui::EventType event_type = ui::ET_TOUCH_MOVED;
@@ -209,6 +216,7 @@
   event->set_hovering(event_type == ui::ET_TOUCH_RELEASED);
   event->latency()->AddLatencyNumberWithTimestamp(
       ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time);
+  event->set_source_device_id(device_id);
   return event;
 }
 
diff --git a/ui/views/win/pen_event_processor.h b/ui/views/win/pen_event_processor.h
index f8b18a9..4095bf0 100644
--- a/ui/views/win/pen_event_processor.h
+++ b/ui/views/win/pen_event_processor.h
@@ -15,6 +15,7 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/sequential_id_generator.h"
 #include "ui/views/views_export.h"
+#include "ui/views/win/pen_id_handler.h"
 
 namespace views {
 
@@ -46,13 +47,15 @@
       UINT32 pointer_id,
       const POINTER_INFO& pointer_info,
       const gfx::Point& point,
-      const ui::PointerDetails& pointer_details);
+      const ui::PointerDetails& pointer_details,
+      int32_t device_id);
   std::unique_ptr<ui::Event> GenerateTouchEvent(
       UINT message,
       UINT32 pointer_id,
       const POINTER_INFO& pointer_info,
       const gfx::Point& point,
-      const ui::PointerDetails& pointer_details);
+      const ui::PointerDetails& pointer_details,
+      int32_t device_id);
 
   raw_ptr<ui::SequentialIDGenerator> id_generator_;
   bool direct_manipulation_enabled_;
@@ -64,6 +67,8 @@
   base::flat_map<UINT32, bool> sent_touch_start_;
 
   absl::optional<ui::PointerId> eraser_pointer_id_;
+
+  PenIdHandler pen_id_handler_;
 };
 
 }  // namespace views
diff --git a/ui/views/win/pen_event_processor_unittest.cc b/ui/views/win/pen_event_processor_unittest.cc
index 3d55b98..90447d3a 100644
--- a/ui/views/win/pen_event_processor_unittest.cc
+++ b/ui/views/win/pen_event_processor_unittest.cc
@@ -4,12 +4,29 @@
 
 #include "ui/views/win/pen_event_processor.h"
 
+#include "base/win/scoped_winrt_initializer.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/sequential_id_generator.h"
 
 namespace views {
 
-TEST(PenProcessorTest, TypicalCaseDMDisabled) {
+class PenProcessorTest : public ::testing::Test {
+ public:
+  PenProcessorTest() = default;
+  ~PenProcessorTest() override = default;
+
+  // testing::Test overrides.
+  void SetUp() override;
+
+ private:
+  base::win::ScopedWinrtInitializer scoped_winrt_initializer_;
+};
+
+void PenProcessorTest::SetUp() {
+  ASSERT_TRUE(scoped_winrt_initializer_.Succeeded());
+}
+
+TEST_F(PenProcessorTest, TypicalCaseDMDisabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ false);
@@ -63,7 +80,7 @@
   EXPECT_EQ(ui::ET_MOUSE_EXITED, event->AsMouseEvent()->type());
 }
 
-TEST(PenProcessorTest, TypicalCaseDMEnabled) {
+TEST_F(PenProcessorTest, TypicalCaseDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -128,7 +145,7 @@
   EXPECT_EQ(ui::ET_MOUSE_EXITED, event->AsMouseEvent()->type());
 }
 
-TEST(PenProcessorTest, UnpairedPointerDownTouchDMEnabled) {
+TEST_F(PenProcessorTest, UnpairedPointerDownTouchDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -146,7 +163,7 @@
   EXPECT_EQ(nullptr, event.get());
 }
 
-TEST(PenProcessorTest, UnpairedPointerDownMouseDMEnabled) {
+TEST_F(PenProcessorTest, UnpairedPointerDownMouseDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -163,7 +180,7 @@
   EXPECT_EQ(nullptr, event.get());
 }
 
-TEST(PenProcessorTest, TouchFlagDMEnabled) {
+TEST_F(PenProcessorTest, TouchFlagDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -193,7 +210,7 @@
   EXPECT_FALSE(event->flags() & ui::EF_LEFT_MOUSE_BUTTON);
 }
 
-TEST(PenProcessorTest, MouseFlagDMEnabled) {
+TEST_F(PenProcessorTest, MouseFlagDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -226,7 +243,7 @@
             event->AsMouseEvent()->changed_button_flags());
 }
 
-TEST(PenProcessorTest, PenEraserFlagDMEnabled) {
+TEST_F(PenProcessorTest, PenEraserFlagDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
@@ -259,7 +276,7 @@
             event->AsTouchEvent()->pointer_details().pointer_type);
 }
 
-TEST(PenProcessorTest, MultiPenDMEnabled) {
+TEST_F(PenProcessorTest, MultiPenDMEnabled) {
   ui::SequentialIDGenerator id_generator(0);
   PenEventProcessor processor(&id_generator,
                               /*direct_manipulation_enabled*/ true);
diff --git a/ui/views/win/pen_id_handler.cc b/ui/views/win/pen_id_handler.cc
new file mode 100644
index 0000000..0da24e03
--- /dev/null
+++ b/ui/views/win/pen_id_handler.cc
@@ -0,0 +1,78 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/win/pen_id_handler.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/com_init_util.h"
+#include "base/win/core_winrt_util.h"
+#include "base/win/hstring_reference.h"
+#include "base/win/win_util.h"
+#include "base/win/windows_version.h"
+
+namespace views {
+
+namespace {
+
+bool PenDeviceApiSupported() {
+  // PenDevice API only works properly on WIN11 or Win10 post v19044.
+  return base::win::GetVersion() > base::win::Version::WIN10_21H2 ||
+         (base::win::GetVersion() == base::win::Version::WIN10_21H2 &&
+          base::win::OSInfo::GetInstance()->version_number().patch >= 1503);
+}
+
+}  // namespace
+
+PenIdHandler::PenIdHandler() {
+  base::win::AssertComInitialized();
+  HRESULT hr = base::win::RoGetActivationFactory(
+      base::win::HStringReference(RuntimeClass_Windows_Devices_Input_PenDevice)
+          .Get(),
+      IID_PPV_ARGS(&pen_device_statics_));
+  if (FAILED(hr)) {
+    pen_device_statics_ = nullptr;
+  }
+}
+
+PenIdHandler::~PenIdHandler() = default;
+
+absl::optional<int32_t> PenIdHandler::TryGetPenUniqueId(UINT32 pointer_id) {
+  if (!PenDeviceApiSupported()) {
+    return absl::nullopt;
+  }
+
+  absl::optional<std::string> guid = TryGetGuid(pointer_id);
+  if (!guid.has_value()) {
+    return absl::nullopt;
+  }
+
+  auto entry = guid_to_id_map_.insert({guid.value(), current_id_});
+  if (entry.second) {
+    current_id_++;
+  }
+  return entry.first->second;
+}
+
+absl::optional<std::string> PenIdHandler::TryGetGuid(UINT32 pointer_id) const {
+  if (!pen_device_statics_) {
+    return absl::nullopt;
+  }
+
+  Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IPenDevice> pen_device;
+  HRESULT hr = pen_device_statics_->GetFromPointerId(pointer_id, &pen_device);
+  // `pen_device` is null if the pen does not support a unique ID.
+  if (FAILED(hr) || !pen_device) {
+    return absl::nullopt;
+  }
+
+  GUID pen_device_guid;
+  hr = pen_device->get_PenId(&pen_device_guid);
+  if (FAILED(hr)) {
+    return absl::nullopt;
+  }
+
+  return base::WideToUTF8(base::win::WStringFromGUID(pen_device_guid));
+}
+
+}  // namespace views
diff --git a/ui/views/win/pen_id_handler.h b/ui/views/win/pen_id_handler.h
new file mode 100644
index 0000000..ab79ce9a
--- /dev/null
+++ b/ui/views/win/pen_id_handler.h
@@ -0,0 +1,56 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_WIN_PEN_ID_HANDLER_H_
+#define UI_VIEWS_WIN_PEN_ID_HANDLER_H_
+
+#include <combaseapi.h>
+#include <stdint.h>
+#include <windows.devices.input.h>
+#include <windows.ui.input.h>
+#include <wrl.h>
+
+#include <string>
+
+#include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/views_export.h"
+
+namespace views {
+
+// This class is responsible for retrieving the unique pen id from Windows,
+// and mapping it to a unique id that will be used by Blink. When the unique
+// id is fetched, the device's GUID is queried first. If unavailable, then
+// the transducer id - which includes the transducer serial number and vendor
+// id - is retrieved. These IDs are then mapped to a separate unique value,
+// which is ultimately returned.
+class VIEWS_EXPORT PenIdHandler {
+ public:
+  PenIdHandler();
+  virtual ~PenIdHandler();
+  absl::optional<int32_t> TryGetPenUniqueId(UINT32 pointer_id);
+  static void OverrideStaticsForTesting(bool override);
+
+ private:
+  friend class FakePenIdHandler;
+  friend class FakePenIdHandlerFakeStatics;
+  FRIEND_TEST_ALL_PREFIXES(PenIdHandlerTest, GetGuidMapping);
+  FRIEND_TEST_ALL_PREFIXES(PenIdHandlerTest, PenDeviceStaticsFailedToSet);
+  FRIEND_TEST_ALL_PREFIXES(PenIdHandlerTest, TryGetGuidHandlesBadStatics);
+
+  // Virtual for unit testing.
+  // Checks if a PenDevice can be retrieved for the `pointer_id` and returns its
+  // GUID if it exists.
+  virtual absl::optional<std::string> TryGetGuid(UINT32 pointer_id) const;
+
+  base::flat_map<std::string, int32_t> guid_to_id_map_;
+  int32_t current_id_ = 0;
+  Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IPenDeviceStatics>
+      pen_device_statics_;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_WIN_PEN_ID_HANDLER_H_
diff --git a/ui/views/win/pen_id_handler_unittest.cc b/ui/views/win/pen_id_handler_unittest.cc
new file mode 100644
index 0000000..09c73b8
--- /dev/null
+++ b/ui/views/win/pen_id_handler_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/win/pen_id_handler.h"
+
+#include "base/win/scoped_winrt_initializer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/win/test_support/fake_ipen_device.h"
+#include "ui/views/win/test_support/fake_ipen_device_statics.h"
+
+namespace views {
+
+namespace {
+
+constexpr int kPointerId1 = 1111;
+constexpr int kPointerId2 = 2222;
+constexpr int kPointerId3 = 3333;
+constexpr int kPointerId4 = 4444;
+
+}  // namespace
+
+class FakePenIdHandler : public PenIdHandler {
+ public:
+  FakePenIdHandler(
+      Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IPenDeviceStatics>
+          pen_device_statics = nullptr) {
+    pen_device_statics_ = pen_device_statics;
+  }
+};
+
+class PenIdHandlerTest : public ::testing::Test {
+ public:
+  PenIdHandlerTest() = default;
+  ~PenIdHandlerTest() override = default;
+
+  // testing::Test overrides.
+  void SetUp() override;
+  void TearDown() override;
+
+ private:
+  base::win::ScopedWinrtInitializer scoped_winrt_initializer_;
+};
+
+void PenIdHandlerTest::SetUp() {
+  ASSERT_TRUE(scoped_winrt_initializer_.Succeeded());
+}
+
+void PenIdHandlerTest::TearDown() {
+  FakeIPenDeviceStatics::GetInstance()->SimulateAllPenDevicesRemoved();
+}
+
+// Tests TryGetPenUniqueId for devices that have a guid. The unique guid should
+// be correctly mapped to a unique pen id, which is the value that is returned
+// by TryGetPenUniqueId.
+TEST_F(PenIdHandlerTest, GetGuidMapping) {
+  Microsoft::WRL::ComPtr<FakeIPenDeviceStatics> pen_device_statics =
+      FakeIPenDeviceStatics::GetInstance();
+  FakePenIdHandler pen_id_handler(pen_device_statics);
+
+  // Make sure Get GUID works correctly.
+  const auto fake_pen_device_1 = Microsoft::WRL::Make<FakeIPenDevice>();
+  const auto fake_pen_device_2 = Microsoft::WRL::Make<FakeIPenDevice>();
+  const auto fake_pen_device_3 = Microsoft::WRL::Make<FakeIPenDevice>();
+
+  pen_device_statics->SimulatePenEventGenerated(kPointerId1, fake_pen_device_1);
+  pen_device_statics->SimulatePenEventGenerated(kPointerId2, fake_pen_device_2);
+  pen_device_statics->SimulatePenEventGenerated(kPointerId3, fake_pen_device_3);
+  pen_device_statics->SimulatePenEventGenerated(kPointerId4, fake_pen_device_1);
+
+  const absl::optional<int32_t> id =
+      pen_id_handler.TryGetPenUniqueId(kPointerId1);
+
+  const absl::optional<int32_t> id2 =
+      pen_id_handler.TryGetPenUniqueId(kPointerId2);
+  EXPECT_NE(id, id2);
+
+  const absl::optional<int32_t> id3 =
+      pen_id_handler.TryGetPenUniqueId(kPointerId3);
+  EXPECT_NE(id2, id3);
+  EXPECT_NE(id, id3);
+
+  // Different pointer id generated from a previously seen device should return
+  // that device's unique id.
+  EXPECT_EQ(id, pen_id_handler.TryGetPenUniqueId(kPointerId4));
+}
+
+// Simulate statics not being set. This should result in TryGetGuid returning
+// absl::nullopt and TryGetTransducerId returning an invalid Transducer ID.
+// Ultimately TryGetPenUniqueId should return null.
+TEST_F(PenIdHandlerTest, PenDeviceStaticsFailedToSet) {
+  FakePenIdHandler pen_id_handler;
+  EXPECT_EQ(pen_id_handler.TryGetGuid(kPointerId1), absl::nullopt);
+  EXPECT_EQ(pen_id_handler.TryGetPenUniqueId(kPointerId1), absl::nullopt);
+}
+
+TEST_F(PenIdHandlerTest, TryGetGuidHandlesBadStatics) {
+  // Make sure `TryGetGUID` fails when there is no ID.
+  Microsoft::WRL::ComPtr<FakeIPenDeviceStatics> pen_device_statics =
+      FakeIPenDeviceStatics::GetInstance();
+  FakePenIdHandler pen_id_handler(pen_device_statics);
+  EXPECT_EQ(pen_id_handler.TryGetGuid(kPointerId1), absl::nullopt);
+
+  // When there is a GUID, it should be plumbed.
+  const auto fake_pen_device = Microsoft::WRL::Make<FakeIPenDevice>();
+  pen_device_statics->SimulatePenEventGenerated(kPointerId1, fake_pen_device);
+  EXPECT_EQ(pen_id_handler.TryGetGuid(kPointerId1), fake_pen_device->GetGuid());
+}
+
+}  // namespace views
diff --git a/ui/views/win/test_support/fake_ipen_device.cc b/ui/views/win/test_support/fake_ipen_device.cc
new file mode 100644
index 0000000..c9dce16
--- /dev/null
+++ b/ui/views/win/test_support/fake_ipen_device.cc
@@ -0,0 +1,32 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/win/test_support/fake_ipen_device.h"
+
+#include <combaseapi.h>
+
+#include <string>
+
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/win_util.h"
+
+namespace views {
+
+FakeIPenDevice::FakeIPenDevice() {
+  // Initialize guid_ with a random GUID.
+  CHECK_EQ(CoCreateGuid(&guid_), S_OK);
+}
+
+FakeIPenDevice::~FakeIPenDevice() = default;
+
+HRESULT FakeIPenDevice::get_PenId(GUID* value) {
+  *value = guid_;
+  return S_OK;
+}
+
+std::string FakeIPenDevice::GetGuid() const {
+  return base::WideToUTF8(base::win::WStringFromGUID(guid_));
+}
+
+}  // namespace views
diff --git a/ui/views/win/test_support/fake_ipen_device.h b/ui/views/win/test_support/fake_ipen_device.h
new file mode 100644
index 0000000..8e0ca19f
--- /dev/null
+++ b/ui/views/win/test_support/fake_ipen_device.h
@@ -0,0 +1,40 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_H_
+#define UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_H_
+
+#include <windows.devices.input.h>
+#include <wrl.h>
+
+#include <string>
+
+namespace views {
+
+class FakeIPenDevice final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<
+              Microsoft::WRL::WinRt | Microsoft::WRL::InhibitRoOriginateError>,
+          ABI::Windows::Devices::Input::IPenDevice> {
+ public:
+  FakeIPenDevice();
+
+  FakeIPenDevice(const FakeIPenDevice&) = delete;
+  FakeIPenDevice& operator=(const FakeIPenDevice&) = delete;
+
+  ~FakeIPenDevice() override;
+
+  // ABI::Windows::Devices::Input::IPenDevice:
+  IFACEMETHODIMP get_PenId(GUID* value) override;
+
+  // Helper method for getting the pen device GUID as a string.
+  std::string GetGuid() const;
+
+ private:
+  GUID guid_;
+};
+
+}  // namespace views
+
+#endif  //  UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_H_
diff --git a/ui/views/win/test_support/fake_ipen_device_statics.cc b/ui/views/win/test_support/fake_ipen_device_statics.cc
new file mode 100644
index 0000000..0f8469be
--- /dev/null
+++ b/ui/views/win/test_support/fake_ipen_device_statics.cc
@@ -0,0 +1,41 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/win/test_support/fake_ipen_device_statics.h"
+
+#include "base/no_destructor.h"
+
+using ABI::Windows::Devices::Input::IPenDevice;
+
+namespace views {
+
+FakeIPenDeviceStatics::FakeIPenDeviceStatics() = default;
+FakeIPenDeviceStatics::~FakeIPenDeviceStatics() = default;
+
+// static
+FakeIPenDeviceStatics* FakeIPenDeviceStatics::GetInstance() {
+  static base::NoDestructor<FakeIPenDeviceStatics> instance;
+  return instance.get();
+}
+
+HRESULT FakeIPenDeviceStatics::GetFromPointerId(UINT32 pointer_id,
+                                                IPenDevice** result) {
+  auto pen_device = pen_device_map_.find(pointer_id);
+  if (pen_device == pen_device_map_.end()) {
+    return HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
+  }
+  return pen_device->second.CopyTo(result);
+}
+
+void FakeIPenDeviceStatics::SimulatePenEventGenerated(
+    UINT32 pointer_id,
+    Microsoft::WRL::ComPtr<IPenDevice> pen_device) {
+  pen_device_map_[pointer_id] = pen_device;
+}
+
+void FakeIPenDeviceStatics::SimulateAllPenDevicesRemoved() {
+  pen_device_map_.clear();
+}
+
+}  // namespace views
diff --git a/ui/views/win/test_support/fake_ipen_device_statics.h b/ui/views/win/test_support/fake_ipen_device_statics.h
new file mode 100644
index 0000000..b404dede
--- /dev/null
+++ b/ui/views/win/test_support/fake_ipen_device_statics.h
@@ -0,0 +1,51 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_STATICS_H_
+#define UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_STATICS_H_
+
+#include <windows.devices.input.h>
+#include <wrl.h>
+
+#include <map>
+
+namespace views {
+
+class FakeIPenDeviceStatics final
+    : public Microsoft::WRL::RuntimeClass<
+          Microsoft::WRL::RuntimeClassFlags<
+              Microsoft::WRL::WinRt | Microsoft::WRL::InhibitRoOriginateError>,
+          ABI::Windows::Devices::Input::IPenDeviceStatics> {
+ public:
+  FakeIPenDeviceStatics();
+
+  FakeIPenDeviceStatics(const FakeIPenDeviceStatics&) = delete;
+  FakeIPenDeviceStatics& operator=(const FakeIPenDeviceStatics&) = delete;
+
+  ~FakeIPenDeviceStatics() final;
+
+  static FakeIPenDeviceStatics* GetInstance();
+
+  // ABI::Windows::Devices::Input::IPenDeviceStatics:
+  IFACEMETHODIMP GetFromPointerId(
+      UINT32 pointer_id,
+      ABI::Windows::Devices::Input::IPenDevice** pen_device) override;
+
+  // Test methods
+  void SimulatePenEventGenerated(
+      UINT32 pointer_id,
+      Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IPenDevice>
+          pen_device);
+  void SimulateAllPenDevicesRemoved();
+
+ private:
+  // Map pointer_id to pen device.
+  std::map<UINT32,
+           Microsoft::WRL::ComPtr<ABI::Windows::Devices::Input::IPenDevice>>
+      pen_device_map_;
+};
+
+}  // namespace views
+
+#endif  //  UI_VIEWS_WIN_TEST_SUPPORT_FAKE_IPEN_DEVICE_STATICS_H_
diff --git a/ui/webui/resources/cr_components/history_clusters/BUILD.gn b/ui/webui/resources/cr_components/history_clusters/BUILD.gn
index eb33fe66..f5cedbba 100644
--- a/ui/webui/resources/cr_components/history_clusters/BUILD.gn
+++ b/ui/webui/resources/cr_components/history_clusters/BUILD.gn
@@ -70,4 +70,5 @@
   grd_resource_path_prefix = rebase_path(".", "//ui/webui/resources")
 
   optimize = optimize_webui
+  enable_source_maps = enable_webui_inline_sourcemaps
 }