diff --git a/AUTHORS b/AUTHORS
index 68f5ef6..1a187dbd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -36,6 +36,7 @@
 Adam Yi <i@adamyi.com>
 Addanki Gandhi Kishor <kishor.ag@samsung.com>
 Adenilson Cavalcanti <a.cavalcanti@samsung.com>
+Adesh Attavar <adesh.attavar@gmail.com>
 Aditi Singh <a20.singh@samsung.com>
 Aditya Agarwal <ad.agarwal@samsung.com>
 Aditya Bhargava <heuristicist@gmail.com>
diff --git a/DEPS b/DEPS
index e0b199a..70877c72 100644
--- a/DEPS
+++ b/DEPS
@@ -299,19 +299,19 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '4baa1abaa62ee7bde3412e8dbef614134978edab',
+  'src_internal_revision': '7bc0f561fe056c19918d9c9efef049525389066e',
   # 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': '83105a7fb6e2c2e3dda07a63f5139cbedc8c81cf',
+  'skia_revision': '0e8023dc0a1a5655703b39454c090b5a004415d6',
   # 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': '017bc6b893e733863245628de6f216922150bed3',
+  'v8_revision': '13036d7a479a2da10ead4bdc99804cd2e93bb201',
   # 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': '92bd0099204c0b5e8c6daecff9c691b895b87433',
+  'angle_revision': '2937ee276cf5bd27fd5254091abdcf1e68a4155b',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -378,7 +378,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': '1e5fa7e62fdeca99624cc64f84edc865a42cd90f',
+  'chromium_variations_revision': 'ff1e3775275922c5f8dd82af21637e99339997e1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -394,7 +394,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '8e82459cfc5a48ed593407748bbef1e6c37ce943',
+  'devtools_frontend_revision': 'd00d10d6f5adcb3c705c4f81b889c59bcb904d28',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -422,7 +422,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '2789c408aa2677e170a7f4f8a044e42a2f5c8bfe',
+  'quiche_revision': 'b05cd0f90dcbfb962056ec04263452aa14120677',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -450,7 +450,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling nearby
   # and whatever else without interference from each other.
-  'nearby_revision': '16cb7f70b3a8b8b04440b7b96534e11df964ca60',
+  'nearby_revision': 'cd4963b0f9c68775372d91dc422c01d703122345',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling securemessage
   # and whatever else without interference from each other.
@@ -818,7 +818,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '542d4f710ed7446ff82fa80babd76cf24a5a83bd',
+    'fd941d8a913faebbdaddb132368e9eb723e0634d',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -980,7 +980,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'N1ABLBt_DnON5SzQTeRtPn5iHl6fXspohb0jh0zCEd8C',
+          'version': 'sKE9EWwTmOTcGm70eYsAYbGu43CFvT4K-aBQl27iDhgC',
       },
     ],
     'condition': 'checkout_android',
@@ -1155,7 +1155,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '462c04f706e5185ca5e4e9bede03ec7daa2fc92f',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'db1410a8915c656c397f6d9111062c9f5a5947db',
       'condition': 'checkout_chromeos',
   },
 
@@ -1190,13 +1190,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '6953ebe3c1825e0f548e8c124a3b85f6c75dbc73',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'cd076ba1b0be061d4446e47f68b3ec53122ce95c',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '7cdb4af2d310e71f3dfc36ccbf901f5944abd5c7',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '7bc9c8f33ac40a5b30c4a4f8486f25afde26058b',
     'condition': 'checkout_src_internal',
   },
 
@@ -1975,7 +1975,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': 'FiqnqaWel3Wvt9OGRi4Jt4kqwnyTz93yomvUTHimFe8C',
+        'version': 'Kpu7GEJn2XxdxtmWXbJWAeu0uzbQIjw68dRkRaC8fk0C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4004,7 +4004,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '61b32446e8a800b02f9bbaf7a2dc49b9d752b034',
+        'ad7dea67e79c6d152370879b33caf38c79c7e160',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/renderer/aw_content_renderer_client.cc b/android_webview/renderer/aw_content_renderer_client.cc
index be102c4..fb597da 100644
--- a/android_webview/renderer/aw_content_renderer_client.cc
+++ b/android_webview/renderer/aw_content_renderer_client.cc
@@ -5,6 +5,7 @@
 #include "android_webview/renderer/aw_content_renderer_client.h"
 
 #include <memory>
+#include <string_view>
 #include <vector>
 
 #include "android_webview/common/aw_switches.h"
@@ -208,9 +209,9 @@
   android_system_error_page::PopulateErrorPageHtml(error, error_html);
 }
 
-uint64_t AwContentRendererClient::VisitedLinkHash(const char* canonical_url,
-                                                  size_t length) {
-  return visited_link_reader_->ComputeURLFingerprint(canonical_url, length);
+uint64_t AwContentRendererClient::VisitedLinkHash(
+    std::string_view canonical_url) {
+  return visited_link_reader_->ComputeURLFingerprint(canonical_url);
 }
 
 bool AwContentRendererClient::IsLinkVisited(uint64_t link_hash) {
diff --git a/android_webview/renderer/aw_content_renderer_client.h b/android_webview/renderer/aw_content_renderer_client.h
index e805cb8..f566da2e 100644
--- a/android_webview/renderer/aw_content_renderer_client.h
+++ b/android_webview/renderer/aw_content_renderer_client.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "android_webview/common/mojom/render_message_filter.mojom.h"
 #include "android_webview/renderer/aw_render_thread_observer.h"
@@ -51,7 +52,7 @@
                         content::mojom::AlternativeErrorPageOverrideInfoPtr
                             alternative_error_page_info,
                         std::string* error_html) override;
-  uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override;
   bool IsLinkVisited(uint64_t link_hash) override;
   void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
   void GetSupportedKeySystems(media::GetSupportedKeySystemsCB cb) override;
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 0ccc128..ad1f82ca 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1832,6 +1832,8 @@
     "system/notification_center/metrics_utils.h",
     "system/notification_center/notification_center_bubble.cc",
     "system/notification_center/notification_center_bubble.h",
+    "system/notification_center/notification_center_controller.cc",
+    "system/notification_center/notification_center_controller.h",
     "system/notification_center/notification_center_tray.cc",
     "system/notification_center/notification_center_tray.h",
     "system/notification_center/notification_grouping_controller.cc",
@@ -2777,6 +2779,7 @@
     "wm/window_transient_descendant_iterator.h",
     "wm/window_util.cc",
     "wm/window_util.h",
+    "wm/wm_constants.h",
     "wm/wm_default_layout_manager.cc",
     "wm/wm_default_layout_manager.h",
     "wm/wm_event.cc",
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index fe458bd..c8534c2 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1440,7 +1440,7 @@
 // Enables Photoshop Web integration with holding space.
 BASE_FEATURE(kHoldingSpacePhotoshopWebIntegration,
              "HoldingSpacePhotoshopWeb",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables holding space icon to be permanently displayed with extended file
 // expiration to increase predictability of the feature.
@@ -3527,7 +3527,8 @@
 }
 
 bool IsGlanceablesV2CalendarViewEnabled() {
-  return base::FeatureList::IsEnabled(kGlanceablesV2CalendarView);
+  return base::FeatureList::IsEnabled(kGlanceablesV2CalendarView) ||
+         AreAnyGlanceablesTimeManagementViewsEnabled();
 }
 
 bool IsGlanceablesV2ErrorMessageEnabled() {
diff --git a/ash/shell.cc b/ash/shell.cc
index 66d93a0..50cb4b6 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -764,7 +764,8 @@
   }
   RemovePreTargetHandler(system_gesture_filter_.get());
   RemoveAccessibilityEventHandler(mouse_cursor_filter_.get());
-  if (features::IsPeripheralCustomizationEnabled()) {
+  if (features::IsPeripheralCustomizationEnabled() ||
+      ::features::IsShortcutCustomizationEnabled()) {
     RemovePreTargetHandler(shortcut_input_handler_.get());
   }
   RemovePreTargetHandler(modality_filter_.get());
diff --git a/ash/system/notification_center/notification_center_bubble.cc b/ash/system/notification_center/notification_center_bubble.cc
index c420fcf..c05a8c3 100644
--- a/ash/system/notification_center/notification_center_bubble.cc
+++ b/ash/system/notification_center/notification_center_bubble.cc
@@ -6,7 +6,9 @@
 
 #include <memory>
 
+#include "ash/constants/ash_features.h"
 #include "ash/shelf/shelf.h"
+#include "ash/system/notification_center/notification_center_controller.h"
 #include "ash/system/notification_center/notification_center_tray.h"
 #include "ash/system/notification_center/views/notification_center_view.h"
 #include "ash/system/tray/tray_bubble_wrapper.h"
@@ -33,8 +35,14 @@
   bubble_view_->SetMaxHeight(CalculateMaxTrayBubbleHeight(
       notification_center_tray_->GetBubbleWindowContainer()));
 
-  notification_center_view_ =
-      bubble_view_->AddChildView(std::make_unique<NotificationCenterView>());
+  if (features::IsNotificationCenterControllerEnabled()) {
+    notification_center_controller_ =
+        std::make_unique<NotificationCenterController>();
+    bubble_view_->AddChildView(notification_center_controller_->CreateView());
+  } else {
+    notification_center_view_ =
+        bubble_view_->AddChildView(std::make_unique<NotificationCenterView>());
+  }
 
   bubble_wrapper_ =
       std::make_unique<TrayBubbleWrapper>(notification_center_tray_);
@@ -45,8 +53,13 @@
 }
 
 void NotificationCenterBubble::ShowBubble() {
+  if (features::IsNotificationCenterControllerEnabled()) {
+    notification_center_controller_->InitView();
+  }
   bubble_wrapper_->ShowBubble(std::move(bubble_view_));
-  notification_center_view_->Init();
+  if (!features::IsNotificationCenterControllerEnabled()) {
+    notification_center_view_->Init();
+  }
   GetBubbleView()->SizeToContents();
 }
 
@@ -58,6 +71,12 @@
   return bubble_wrapper_->GetBubbleWidget();
 }
 
+NotificationCenterView* NotificationCenterBubble::GetNotificationCenterView() {
+  return features::IsNotificationCenterControllerEnabled()
+             ? notification_center_controller_->GetNotificationCenterView()
+             : notification_center_view_.get();
+}
+
 void NotificationCenterBubble::UpdateBubbleBounds() {
   auto* bubble_view = GetBubbleView();
   bubble_view->SetMaxHeight(CalculateMaxTrayBubbleHeight(
diff --git a/ash/system/notification_center/notification_center_bubble.h b/ash/system/notification_center/notification_center_bubble.h
index 74726185..6b15f3a 100644
--- a/ash/system/notification_center/notification_center_bubble.h
+++ b/ash/system/notification_center/notification_center_bubble.h
@@ -17,6 +17,7 @@
 
 namespace ash {
 
+class NotificationCenterController;
 class NotificationCenterTray;
 class NotificationCenterView;
 class TrayBubbleView;
@@ -44,9 +45,10 @@
   TrayBubbleView* GetBubbleView();
   views::Widget* GetBubbleWidget();
 
-  NotificationCenterView* notification_center_view() {
-    return notification_center_view_;
-  }
+  // Based on the `NotificationCenterController` feature:
+  // Returns `notification_center_view_` when the feature is disabled.
+  // Returns the view cached in `notification_center_controller_` when enabled.
+  NotificationCenterView* GetNotificationCenterView();
 
  private:
   friend class NotificationCenterTestApi;
@@ -62,8 +64,14 @@
 
   // The main view responsible for showing all notification content in this
   // bubble. Owned by `TrayBubbleView`.
+  // Used when `NotificationCenterController` is disabled.
   raw_ptr<NotificationCenterView> notification_center_view_ = nullptr;
 
+  // The controller responsible for managing the NotificationCenterView and its
+  // children including the `StackedNotificationBar` and `NotificationListView`.
+  // Used when `NotificationCenterController` is enabled.
+  std::unique_ptr<NotificationCenterController> notification_center_controller_;
+
   std::unique_ptr<TrayBubbleView> bubble_view_;
   std::unique_ptr<TrayBubbleWrapper> bubble_wrapper_;
 };
diff --git a/ash/system/notification_center/notification_center_bubble_unittest.cc b/ash/system/notification_center/notification_center_bubble_unittest.cc
index 8d5366d..8d395f1 100644
--- a/ash/system/notification_center/notification_center_bubble_unittest.cc
+++ b/ash/system/notification_center/notification_center_bubble_unittest.cc
@@ -7,19 +7,21 @@
 #include <cstdint>
 #include <string>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
-#include "ash/system/notification_center/views/ash_notification_view.h"
 #include "ash/system/notification_center/message_center_utils.h"
 #include "ash/system/notification_center/notification_center_bubble.h"
 #include "ash/system/notification_center/notification_center_test_api.h"
+#include "ash/system/notification_center/views/ash_notification_view.h"
 #include "ash/system/notification_center/views/notification_center_view.h"
 #include "ash/system/notification_center/views/notification_list_view.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
@@ -28,30 +30,52 @@
 
 namespace ash {
 
-class NotificationCenterBubbleTest : public AshTestBase {
+class NotificationCenterBubbleTestBase : public AshTestBase {
  public:
-  NotificationCenterBubbleTest()
-      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
-  NotificationCenterBubbleTest(const NotificationCenterBubbleTest&) = delete;
-  NotificationCenterBubbleTest& operator=(const NotificationCenterBubbleTest&) =
-      delete;
-  ~NotificationCenterBubbleTest() override = default;
+  NotificationCenterBubbleTestBase(bool enable_notification_center_controller)
+      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+        enable_notification_center_controller_(
+            enable_notification_center_controller) {
+    scoped_feature_list_.InitWithFeatureState(
+        features::kNotificationCenterController,
+        IsNotificationCenterControllerEnabled());
+  }
 
   void SetUp() override {
     AshTestBase::SetUp();
-
     test_api_ = std::make_unique<NotificationCenterTestApi>();
   }
 
   NotificationCenterTestApi* test_api() { return test_api_.get(); }
 
+  bool IsNotificationCenterControllerEnabled() const {
+    return enable_notification_center_controller_;
+  }
+
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<NotificationCenterTestApi> test_api_;
+  bool enable_notification_center_controller_ = false;
 };
 
+class NotificationCenterBubbleTest
+    : public NotificationCenterBubbleTestBase,
+      public testing::WithParamInterface<
+          /*enable_notification_center_controller=*/bool> {
+ public:
+  NotificationCenterBubbleTest()
+      : NotificationCenterBubbleTestBase(
+            /*enable_notification_center_controller=*/GetParam()) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    NotificationCenterBubbleTest,
+    /*enable_notification_center_controller=*/testing::Bool());
+
 // Tests that the notification bubble does not get cut off by the top of the
 // screen on the launcher homescreen in tablet mode; see b/278471988.
-TEST_F(NotificationCenterBubbleTest,
+TEST_P(NotificationCenterBubbleTest,
        TopOfBubbleConstrainedByTopOfDisplayInTabletModeHomescreen) {
   // Set the display to some known size.
   UpdateDisplay("1200x800");
@@ -77,7 +101,7 @@
       0);
 }
 
-TEST_F(NotificationCenterBubbleTest, BubbleHeightConstrainedByDisplay) {
+TEST_P(NotificationCenterBubbleTest, BubbleHeightConstrainedByDisplay) {
   const int display_height = 800;
   UpdateDisplay("1200x" + base::NumberToString(display_height));
 
@@ -96,7 +120,7 @@
             display_height);
 }
 
-TEST_F(NotificationCenterBubbleTest, BubbleHeightUpdatedByDisplaySizeChange) {
+TEST_P(NotificationCenterBubbleTest, BubbleHeightUpdatedByDisplaySizeChange) {
   UpdateDisplay("800x600");
 
   // Add a large number of notifications to overflow the scroll view in the
@@ -120,7 +144,7 @@
   EXPECT_EQ(current_bounds.width(), previous_bounds.width());
 }
 
-TEST_F(NotificationCenterBubbleTest, BubbleHeightUpdatedByDisplayRotation) {
+TEST_P(NotificationCenterBubbleTest, BubbleHeightUpdatedByDisplayRotation) {
   const int display_width = 1000;
   const int display_height = 600;
   UpdateDisplay(base::NumberToString(display_width) + "x" +
@@ -158,7 +182,7 @@
 
 // Tests that notifications from a single notifier id are grouped in a single
 // parent notification view.
-TEST_F(NotificationCenterBubbleTest, NotificationsGroupingBasic) {
+TEST_P(NotificationCenterBubbleTest, NotificationsGroupingBasic) {
   const std::string source_url = "http://test-url.com";
 
   std::string id0, id1;
@@ -181,7 +205,7 @@
   EXPECT_TRUE(parent_notification_view->FindGroupNotificationView(id1));
 }
 
-TEST_F(NotificationCenterBubbleTest,
+TEST_P(NotificationCenterBubbleTest,
        NotificationCollapseStatePreservedFromPopup) {
   std::string id0 = test_api()->AddNotification();
 
@@ -196,7 +220,7 @@
   EXPECT_FALSE(test_api()->GetNotificationViewForId(id0)->IsExpanded());
 }
 
-TEST_F(NotificationCenterBubbleTest,
+TEST_P(NotificationCenterBubbleTest,
        NotificationExpandStatePreservedAcrossDisplays) {
   UpdateDisplay("600x500,600x500");
 
@@ -237,7 +261,7 @@
                   ->IsExpanded());
 }
 
-TEST_F(NotificationCenterBubbleTest, LockScreenNotificationVisibility) {
+TEST_P(NotificationCenterBubbleTest, LockScreenNotificationVisibility) {
   std::string system_id, id;
   system_id = test_api()->AddSystemNotification();
   id = test_api()->AddNotification();
@@ -253,7 +277,7 @@
 
 // Tests that unlocking the device automatically closes the notification bubble.
 // See b/287622547.
-TEST_F(NotificationCenterBubbleTest, UnlockClosesBubble) {
+TEST_P(NotificationCenterBubbleTest, UnlockClosesBubble) {
   // Add a notification so that the notification tray will be visible on the
   // lock screen.
   test_api()->AddNotification();
@@ -274,7 +298,7 @@
   EXPECT_FALSE(test_api()->IsBubbleShown());
 }
 
-TEST_F(NotificationCenterBubbleTest, LargeNotificationExpand) {
+TEST_P(NotificationCenterBubbleTest, LargeNotificationExpand) {
   const std::string url = "http://test-url.com/";
   std::string id0 = test_api()->AddNotificationWithSourceUrl(url);
 
@@ -312,17 +336,16 @@
 }
 
 class NotificationCenterBubbleMultiDisplayTest
-    : public NotificationCenterBubbleTest,
+    : public NotificationCenterBubbleTestBase,
       public testing::WithParamInterface<
           std::tuple</* Primary display height */ int,
-                     /* Secondary display height */ int>> {
+                     /* Secondary display height */ int,
+                     /* enable_notification_center_controller */ bool>> {
  public:
-  NotificationCenterBubbleMultiDisplayTest() = default;
-  NotificationCenterBubbleMultiDisplayTest(
-      const NotificationCenterBubbleMultiDisplayTest&) = delete;
-  NotificationCenterBubbleMultiDisplayTest& operator=(
-      const NotificationCenterBubbleMultiDisplayTest&) = delete;
-  ~NotificationCenterBubbleMultiDisplayTest() override = default;
+  NotificationCenterBubbleMultiDisplayTest()
+      : NotificationCenterBubbleTestBase(
+            /*enable_notification_center_controller=*/std::get<2>(GetParam())) {
+  }
 
  protected:
   int GetPrimaryDisplayHeight() { return std::get<0>(GetParam()); }
@@ -333,11 +356,14 @@
                          NotificationCenterBubbleMultiDisplayTest,
                          testing::Values(
                              // Short primary display, tall secondary display
-                             std::make_tuple(600, 1600),
+                             std::make_tuple(600, 1600, false),
+                             std::make_tuple(600, 1600, true),
                              // Tall primary display, short secondary display
-                             std::make_tuple(1600, 600),
+                             std::make_tuple(1600, 600, false),
+                             std::make_tuple(1600, 600, true),
                              // Same primary and secondary display heights
-                             std::make_tuple(600, 600)));
+                             std::make_tuple(600, 600, false),
+                             std::make_tuple(600, 600, true)));
 
 // Tests that the height of the bubble is constrained according to the
 // parameters of the display it is being shown on.
diff --git a/ash/system/notification_center/notification_center_controller.cc b/ash/system/notification_center/notification_center_controller.cc
new file mode 100644
index 0000000..f73f8a8
--- /dev/null
+++ b/ash/system/notification_center/notification_center_controller.cc
@@ -0,0 +1,37 @@
+// Copyright 2024 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/system/notification_center/notification_center_controller.h"
+
+#include "ash/constants/ash_features.h"
+#include "ash/system/notification_center/views/notification_center_view.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+NotificationCenterController::NotificationCenterController() {
+  CHECK(features::IsNotificationCenterControllerEnabled());
+}
+
+NotificationCenterController::~NotificationCenterController() = default;
+
+std::unique_ptr<views::View> NotificationCenterController::CreateView() {
+  auto notification_center_view = std::make_unique<NotificationCenterView>();
+  notification_center_view_tracker_.SetView(notification_center_view.get());
+  return std::move(notification_center_view);
+}
+
+void NotificationCenterController::InitView() {
+  auto* notification_center_view = GetNotificationCenterView();
+  CHECK(notification_center_view);
+  notification_center_view->Init();
+}
+
+NotificationCenterView*
+NotificationCenterController::GetNotificationCenterView() {
+  return static_cast<NotificationCenterView*>(
+      notification_center_view_tracker_.view());
+}
+
+}  // namespace ash
diff --git a/ash/system/notification_center/notification_center_controller.h b/ash/system/notification_center/notification_center_controller.h
new file mode 100644
index 0000000..8861927
--- /dev/null
+++ b/ash/system/notification_center/notification_center_controller.h
@@ -0,0 +1,48 @@
+// Copyright 2024 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_SYSTEM_NOTIFICATION_CENTER_NOTIFICATION_CENTER_CONTROLLER_H_
+#define ASH_SYSTEM_NOTIFICATION_CENTER_NOTIFICATION_CENTER_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/memory/raw_ptr.h"
+#include "ui/views/view_tracker.h"
+
+namespace views {
+class View;
+}  // namespace views
+
+namespace ash {
+
+class NotificationCenterView;
+
+// Manages and updates `NotificationCenterView`.
+class ASH_EXPORT NotificationCenterController {
+ public:
+  NotificationCenterController();
+
+  NotificationCenterController(const NotificationCenterController&) = delete;
+  NotificationCenterController& operator=(const NotificationCenterController&) =
+      delete;
+
+  ~NotificationCenterController();
+
+  // Creates a `NotificationCenterView` object and returns it so it can be added
+  // to the parent bubble view.
+  std::unique_ptr<views::View> CreateView();
+
+  // Inits the tracked `NotificationCenterView`.
+  void InitView();
+
+  // Returns the view tracked by `notification_center_view_tracker_`.
+  NotificationCenterView* GetNotificationCenterView();
+
+ private:
+  // View tracker to safely access `NotificationCenterView`.
+  views::ViewTracker notification_center_view_tracker_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_SYSTEM_NOTIFICATION_CENTER_NOTIFICATION_CENTER_CONTROLLER_H_
diff --git a/ash/system/notification_center/notification_center_test_api.cc b/ash/system/notification_center/notification_center_test_api.cc
index 44a1bc1..d0bcf85 100644
--- a/ash/system/notification_center/notification_center_test_api.cc
+++ b/ash/system/notification_center/notification_center_test_api.cc
@@ -3,8 +3,10 @@
 // found in the LICENSE file.
 
 #include "ash/system/notification_center/notification_center_test_api.h"
+
 #include <cstdint>
 
+#include "ash/constants/ash_features.h"
 #include "ash/focus_cycler.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
@@ -13,9 +15,9 @@
 #include "ash/system/notification_center/message_center_utils.h"
 #include "ash/system/notification_center/notification_center_bubble.h"
 #include "ash/system/notification_center/notification_center_tray.h"
+#include "ash/system/notification_center/stacked_notification_bar.h"
 #include "ash/system/notification_center/views/notification_center_view.h"
 #include "ash/system/notification_center/views/notification_list_view.h"
-#include "ash/system/notification_center/stacked_notification_bar.h"
 #include "ash/system/unified/notification_counter_view.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "base/ranges/algorithm.h"
@@ -317,7 +319,7 @@
     return nullptr;
   }
 
-  return GetTrayOnDisplay(display_id)->bubble_->notification_center_view_.get();
+  return GetTrayOnDisplay(display_id)->bubble_->GetNotificationCenterView();
 }
 
 NotificationCenterView* NotificationCenterTestApi::GetNotificationCenterView() {
@@ -335,9 +337,10 @@
 }
 
 views::View* NotificationCenterTestApi::GetClearAllButton() {
-  return GetTray()
-      ->bubble_->notification_center_view_->notification_bar_
-      ->clear_all_button_;
+  auto* notification_center_view = GetNotificationCenterView();
+  return notification_center_view
+             ? notification_center_view->notification_bar_->clear_all_button_
+             : nullptr;
 }
 
 std::string NotificationCenterTestApi::NotificationIdToParentNotificationId(
@@ -366,8 +369,8 @@
     int64_t display_id) {
   DCHECK(message_center::MessageCenter::Get()->IsMessageCenterVisible());
 
-  return GetTrayOnDisplay(display_id)
-      ->bubble_->notification_center_view_->notification_list_view();
+  return GetNotificationCenterViewOnDisplay(display_id)
+      ->notification_list_view();
 }
 
 std::unique_ptr<message_center::Notification>
diff --git a/ash/system/notification_center/notification_center_tray.cc b/ash/system/notification_center/notification_center_tray.cc
index 211dc05..4878bf8 100644
--- a/ash/system/notification_center/notification_center_tray.cc
+++ b/ash/system/notification_center/notification_center_tray.cc
@@ -100,8 +100,14 @@
 }
 
 NotificationListView* NotificationCenterTray::GetNotificationListView() {
-  return bubble_ ? bubble_->notification_center_view()->notification_list_view()
-                 : nullptr;
+  if (!bubble_) {
+    return nullptr;
+  }
+
+  auto* notification_center_view = bubble_->GetNotificationCenterView();
+  return notification_center_view
+             ? notification_center_view->notification_list_view()
+             : nullptr;
 }
 
 bool NotificationCenterTray::IsBubbleShown() const {
diff --git a/ash/system/notification_center/notification_center_tray_unittest.cc b/ash/system/notification_center/notification_center_tray_unittest.cc
index c6841fc..eea4be7 100644
--- a/ash/system/notification_center/notification_center_tray_unittest.cc
+++ b/ash/system/notification_center/notification_center_tray_unittest.cc
@@ -32,28 +32,50 @@
 constexpr char kNotificationCenterTrayNoNotificationsToastId[] =
     "notification_center_tray_toast_ids.no_notifications";
 
-class NotificationCenterTrayTest : public AshTestBase {
+class NotificationCenterTrayTestBase : public AshTestBase {
  public:
-  NotificationCenterTrayTest() = default;
-  NotificationCenterTrayTest(const NotificationCenterTrayTest&) = delete;
-  NotificationCenterTrayTest& operator=(const NotificationCenterTrayTest&) =
-      delete;
-  ~NotificationCenterTrayTest() override = default;
+  NotificationCenterTrayTestBase(bool enable_notification_center_controller)
+      : enable_notification_center_controller_(
+            enable_notification_center_controller) {
+    scoped_feature_list_.InitWithFeatureState(
+        features::kNotificationCenterController,
+        IsNotificationCenterControllerEnabled());
+  }
 
   void SetUp() override {
     AshTestBase::SetUp();
-
     test_api_ = std::make_unique<NotificationCenterTestApi>();
   }
 
   NotificationCenterTestApi* test_api() { return test_api_.get(); }
 
+  bool IsNotificationCenterControllerEnabled() const {
+    return enable_notification_center_controller_;
+  }
+
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<NotificationCenterTestApi> test_api_;
+  bool enable_notification_center_controller_ = false;
 };
 
+class NotificationCenterTrayTest
+    : public NotificationCenterTrayTestBase,
+      public testing::WithParamInterface<
+          /*enable_notification_center_controller=*/bool> {
+ public:
+  NotificationCenterTrayTest()
+      : NotificationCenterTrayTestBase(
+            /*enable_notification_center_controller=*/GetParam()) {}
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    NotificationCenterTrayTest,
+    /*enable_notification_center_controller=*/testing::Bool());
+
 // Test the initial state.
-TEST_F(NotificationCenterTrayTest, ShowTrayButtonOnNotificationAvailability) {
+TEST_P(NotificationCenterTrayTest, ShowTrayButtonOnNotificationAvailability) {
   EXPECT_FALSE(test_api()->GetTray()->GetVisible());
 
   std::string id = test_api()->AddNotification();
@@ -65,7 +87,7 @@
 }
 
 // Bubble creation and destruction.
-TEST_F(NotificationCenterTrayTest, ShowAndHideBubbleOnUserInteraction) {
+TEST_P(NotificationCenterTrayTest, ShowAndHideBubbleOnUserInteraction) {
   test_api()->AddNotification();
 
   auto* tray = test_api()->GetTray();
@@ -81,7 +103,7 @@
 
 // Hitting escape after opening the bubble should destroy the bubble
 // gracefully.
-TEST_F(NotificationCenterTrayTest, EscapeClosesBubble) {
+TEST_P(NotificationCenterTrayTest, EscapeClosesBubble) {
   auto* tray = test_api()->GetTray();
   LeftClickOn(tray);
   PressAndReleaseKey(ui::KeyboardCode::VKEY_ESCAPE);
@@ -91,7 +113,7 @@
 
 // Removing all notifications by hitting the `clear_all_button_` should result
 // in the bubble being destroyed and the tray bubble going invisible.
-TEST_F(NotificationCenterTrayTest,
+TEST_P(NotificationCenterTrayTest,
        ClearAllNotificationsDestroysBubbleAndHidesTray) {
   test_api()->AddNotification();
   test_api()->AddNotification();
@@ -107,8 +129,8 @@
 
 // The last notification being removed directly by the
 // `message_center::MessageCenter` API should result in the bubble being
-// destroyed and tray visibilty being updated.
-TEST_F(NotificationCenterTrayTest, NotificationsRemovedByMessageCenterApi) {
+// destroyed and tray visibility being updated.
+TEST_P(NotificationCenterTrayTest, NotificationsRemovedByMessageCenterApi) {
   std::string id = test_api()->AddNotification();
   test_api()->RemoveNotification(id);
 
@@ -119,7 +141,7 @@
 // When the only notifications present are all in the same group, removing the
 // parent notification of that group should result in the bubble being destroyed
 // and the tray being hidden.
-TEST_F(NotificationCenterTrayTest,
+TEST_P(NotificationCenterTrayTest,
        RemovingGroupParentDestroysBubbleAndHidesTray) {
   // Add two notifications that belong to the same group.
   const std::string url = "http://test-url.com";
@@ -145,7 +167,7 @@
 // Tests that clicking on the tray immediately after all notifications have been
 // removed does not result in an empty bubble being shown. This addresses
 // b/293174118.
-TEST_F(NotificationCenterTrayTest, ClickOnTrayAfterRemovingNotifications) {
+TEST_P(NotificationCenterTrayTest, ClickOnTrayAfterRemovingNotifications) {
   // Add a notification to make the tray visible. Note that animations complete
   // immediately in this part of the test.
   std::string id = test_api()->AddNotification();
@@ -173,7 +195,7 @@
 
 // Tests that opening the bubble results in existing popups being dismissed
 // and no new ones being created.
-TEST_F(NotificationCenterTrayTest, NotificationPopupsHiddenWithBubble) {
+TEST_P(NotificationCenterTrayTest, NotificationPopupsHiddenWithBubble) {
   // Adding a notification should generate a popup.
   std::string id = test_api()->AddNotification();
   EXPECT_TRUE(test_api()->IsPopupShown(id));
@@ -189,7 +211,7 @@
 }
 
 // Tests that popups are shown after the notification center is closed.
-TEST_F(NotificationCenterTrayTest, NotificationPopupsShownAfterBubbleClose) {
+TEST_P(NotificationCenterTrayTest, NotificationPopupsShownAfterBubbleClose) {
   test_api()->AddNotification();
 
   // Open and close bubble to dismiss existing popups.
@@ -202,7 +224,7 @@
 }
 
 // Keyboard accelerator shows/hides the bubble.
-TEST_F(NotificationCenterTrayTest, AcceleratorTogglesBubble) {
+TEST_P(NotificationCenterTrayTest, AcceleratorTogglesBubble) {
   test_api()->AddNotification();
   EXPECT_FALSE(test_api()->IsBubbleShown());
   // Pressing the accelerator should show the bubble.
@@ -216,7 +238,7 @@
 }
 
 // Keyboard accelerator shows a toast when there are no notifications.
-TEST_F(NotificationCenterTrayTest, AcceleratorShowsToastWhenNoNotifications) {
+TEST_P(NotificationCenterTrayTest, AcceleratorShowsToastWhenNoNotifications) {
   ASSERT_EQ(test_api()->GetNotificationCount(), 0u);
   EXPECT_FALSE(ToastManager::Get()->IsToastShown(
       kNotificationCenterTrayNoNotificationsToastId));
@@ -230,7 +252,7 @@
 
 // Tests that the bubble automatically hides if it is visible when another
 // bubble becomes visible, and otherwise does not automatically show or hide.
-TEST_F(NotificationCenterTrayTest, BubbleHideBehavior) {
+TEST_P(NotificationCenterTrayTest, BubbleHideBehavior) {
   // Basic verification test that the notification center tray bubble can
   // show/hide itself when no other bubbles are visible.
   EXPECT_FALSE(test_api()->IsBubbleShown());
@@ -259,7 +281,7 @@
 
 // Tests that visibility of the Do not disturb icon changes with Do not disturb
 // mode.
-TEST_F(NotificationCenterTrayTest, DoNotDisturbIconVisibility) {
+TEST_P(NotificationCenterTrayTest, DoNotDisturbIconVisibility) {
   // Test the case where the tray is not initially visible.
   ASSERT_FALSE(test_api()->IsTrayShown());
   EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
@@ -282,7 +304,7 @@
   EXPECT_FALSE(test_api()->IsDoNotDisturbIconShown());
 }
 
-TEST_F(NotificationCenterTrayTest, DoNotDisturbUpdatesPinnedIcons) {
+TEST_P(NotificationCenterTrayTest, DoNotDisturbUpdatesPinnedIcons) {
   test_api()->AddPinnedNotification();
   EXPECT_TRUE(test_api()->IsNotificationIconShown());
 
@@ -293,7 +315,7 @@
   EXPECT_TRUE(test_api()->IsNotificationIconShown());
 }
 
-TEST_F(NotificationCenterTrayTest, NoPrivacyIndicatorsWhenVcEnabled) {
+TEST_P(NotificationCenterTrayTest, NoPrivacyIndicatorsWhenVcEnabled) {
   // No privacy indicators when `kVideoConference` is enabled.
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
@@ -308,7 +330,7 @@
 
 // Tests that the focus ring is visible and has proper size when the
 // notification center tray is focused.
-TEST_F(NotificationCenterTrayTest, FocusRing) {
+TEST_P(NotificationCenterTrayTest, FocusRing) {
   // Add a notification to make the notification center tray visible.
   test_api()->AddNotification();
   ASSERT_TRUE(test_api()->IsTrayShown());
@@ -328,7 +350,7 @@
 
 // Tests that removing all notifications while the lock screen is showing hides
 // the tray.
-TEST_F(NotificationCenterTrayTest,
+TEST_P(NotificationCenterTrayTest,
        RemovingAllNotificationsOnLockScreenHidesTray) {
   // Add a notification to make the notification center tray visible.
   const std::string id = test_api()->AddNotification();
@@ -346,7 +368,7 @@
 
 // Tests that adding an initial notification while the lock screen is showing
 // shows the tray.
-TEST_F(NotificationCenterTrayTest, AddingNotificationOnLockScreenShowsTray) {
+TEST_P(NotificationCenterTrayTest, AddingNotificationOnLockScreenShowsTray) {
   // Show the lock screen.
   BlockUserSession(BLOCKED_BY_LOCK_SCREEN);
   ASSERT_FALSE(test_api()->IsTrayShown());
@@ -361,7 +383,7 @@
 // Tests that `NotificationCounterView` is not still visible on secondary
 // display after logging in with a pinned notification present. This covers
 // b/284139989.
-TEST_F(NotificationCenterTrayTest,
+TEST_P(NotificationCenterTrayTest,
        NotificationCounterVisibilityForMultiDisplay) {
   // The behavior under test relies on `TrayItemView` animations being
   // scheduled, but `TrayItemView` animations are bypassed when the animation
@@ -406,28 +428,33 @@
 }
 
 // Test fixture that disables notification popups.
-class NotificationCenterTrayNoPopupsTest : public NotificationCenterTrayTest {
+class NotificationCenterTrayNoPopupsTest
+    : public NotificationCenterTrayTestBase,
+      public testing::WithParamInterface<
+          /*enable_notification_center_controller=*/bool> {
  public:
-  NotificationCenterTrayNoPopupsTest() = default;
-  NotificationCenterTrayNoPopupsTest(
-      const NotificationCenterTrayNoPopupsTest&) = delete;
-  NotificationCenterTrayNoPopupsTest& operator=(
-      const NotificationCenterTrayNoPopupsTest&) = delete;
-  ~NotificationCenterTrayNoPopupsTest() override = default;
+  NotificationCenterTrayNoPopupsTest()
+      : NotificationCenterTrayTestBase(
+            /*enable_notification_center_controller=*/GetParam()) {}
 
   void SetUp() override {
     base::CommandLine::ForCurrentProcess()->AppendSwitch(
         switches::kSuppressMessageCenterPopups);
-    NotificationCenterTrayTest::SetUp();
+    NotificationCenterTrayTestBase::SetUp();
   }
 };
 
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    NotificationCenterTrayNoPopupsTest,
+    /*enable_notification_center_controller=*/testing::Bool());
+
 // Tests that `NotificationCenterTray`'s `TrayItemView`s show up when adding a
 // secondary display. Notification popups are disabled for this test because the
 // presence of a popup actually hides the issue (i.e. the secondary display's
 // `NotificationCenterTray`'s `TrayItemView`s work as intended when a popup is
 // present). This covers b/281158734.
-TEST_F(NotificationCenterTrayNoPopupsTest,
+TEST_P(NotificationCenterTrayNoPopupsTest,
        TrayItemsVisibleWhenAddingSecondaryDisplay) {
   // Start with one display.
   UpdateDisplay("800x799");
diff --git a/ash/system/notification_center/views/notification_center_view_unittest.cc b/ash/system/notification_center/views/notification_center_view_unittest.cc
index 3b8f1f2..60713868 100644
--- a/ash/system/notification_center/views/notification_center_view_unittest.cc
+++ b/ash/system/notification_center/views/notification_center_view_unittest.cc
@@ -39,16 +39,17 @@
 
 namespace ash {
 
-class NotificationCenterViewTest : public AshTestBase,
-                                   public views::ViewObserver {
+class NotificationCenterViewTest
+    : public AshTestBase,
+      public views::ViewObserver,
+      public testing::WithParamInterface<
+          /*enable_notification_center_controller=*/bool> {
  public:
-  NotificationCenterViewTest() = default;
-
-  NotificationCenterViewTest(const NotificationCenterViewTest&) = delete;
-  NotificationCenterViewTest& operator=(const NotificationCenterViewTest&) =
-      delete;
-
-  ~NotificationCenterViewTest() override = default;
+  NotificationCenterViewTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        features::kNotificationCenterController,
+        IsNotificationCenterControllerEnabled());
+  }
 
   // AshTestBase:
   void SetUp() override {
@@ -73,6 +74,8 @@
     ++size_changed_count_;
   }
 
+  bool IsNotificationCenterControllerEnabled() const { return GetParam(); }
+
  protected:
   // Adds more than enough notifications to make the message center scrollable.
   std::vector<std::string> AddManyNotifications() {
@@ -212,10 +215,15 @@
   int size_changed_count_ = 0;
 
   std::unique_ptr<NotificationCenterTestApi> test_api_;
-  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(NotificationCenterViewTest, ContentsRelayout) {
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    NotificationCenterViewTest,
+    /*enable_notification_center_controller=*/testing::Bool());
+
+TEST_P(NotificationCenterViewTest, ContentsRelayout) {
   std::vector<std::string> ids = AddManyNotifications();
   test_api()->ToggleBubble();
   EXPECT_TRUE(notification_center_view()->GetVisible());
@@ -237,7 +245,7 @@
             GetNotificationListView()->height());
 }
 
-TEST_F(NotificationCenterViewTest, VisibleWhenLocked) {
+TEST_P(NotificationCenterViewTest, VisibleWhenLocked) {
   // This test is only valid if the lock screen feature is enabled.
   // TODO(yoshiki): Clean up after the feature is launched crbug.com/913764.
   if (!features::IsLockScreenNotificationsEnabled()) {
@@ -261,7 +269,7 @@
   EXPECT_TRUE(notification_center_view()->GetVisible());
 }
 
-TEST_F(NotificationCenterViewTest, ClearAllPressed) {
+TEST_P(NotificationCenterViewTest, ClearAllPressed) {
   test_api()->AddNotification();
   test_api()->AddNotification();
   test_api()->ToggleBubble();
@@ -273,7 +281,7 @@
   notification_center_view()->ClearAllNotifications();
 }
 
-TEST_F(NotificationCenterViewTest, InitialPosition) {
+TEST_P(NotificationCenterViewTest, InitialPosition) {
   test_api()->AddNotification();
   test_api()->AddNotification();
   test_api()->ToggleBubble();
@@ -284,7 +292,7 @@
             notification_center_view()->bounds().height());
 }
 
-TEST_F(NotificationCenterViewTest, InitialPositionMaxOut) {
+TEST_P(NotificationCenterViewTest, InitialPositionMaxOut) {
   AddManyNotifications();
   test_api()->ToggleBubble();
   EXPECT_TRUE(notification_center_view()->GetVisible());
@@ -295,7 +303,7 @@
 }
 
 // Tests basic layout of the StackingNotificationBar.
-TEST_F(NotificationCenterViewTest, StackingCounterLabelLayout) {
+TEST_P(NotificationCenterViewTest, StackingCounterLabelLayout) {
   UpdateDisplay("800x500");
 
   AddManyNotifications();
@@ -318,7 +326,7 @@
 }
 
 // Tests that the NotificationBarLabel is invisible when scrolled to the top.
-TEST_F(NotificationCenterViewTest, StackingCounterLabelInvisible) {
+TEST_P(NotificationCenterViewTest, StackingCounterLabelInvisible) {
   UpdateDisplay("800x500");
 
   AddManyNotifications();
@@ -337,7 +345,7 @@
 
 // Tests that the NotificationBarLabel is visible when there are enough excess
 // notifications.
-TEST_F(NotificationCenterViewTest, StackingCounterLabelVisible) {
+TEST_P(NotificationCenterViewTest, StackingCounterLabelVisible) {
   UpdateDisplay("800x500");
 
   AddManyNotifications();
@@ -349,7 +357,7 @@
 }
 
 // Tests that the +n notifications label hides after being shown.
-TEST_F(NotificationCenterViewTest, StackingCounterLabelHidesAfterShown) {
+TEST_P(NotificationCenterViewTest, StackingCounterLabelHidesAfterShown) {
   UpdateDisplay("800x500");
 
   AddManyNotifications();
@@ -383,7 +391,7 @@
 // time (this prevents the user from over-scrolling and showing multiple
 // animations when they scroll very quickly). Before, users could scroll fast
 // and have a large amount of icons, instead of keeping it to 3.
-TEST_F(NotificationCenterViewTest, StackingIconsNeverMoreThanThree) {
+TEST_P(NotificationCenterViewTest, StackingIconsNeverMoreThanThree) {
   for (int i = 0; i < 20; ++i) {
     test_api()->AddNotification();
   }
@@ -419,7 +427,7 @@
   }
 }
 
-TEST_F(NotificationCenterViewTest, StackingCounterLabelRelaidOutOnScroll) {
+TEST_P(NotificationCenterViewTest, StackingCounterLabelRelaidOutOnScroll) {
   // Open the message center at the top of the notification list so the stacking
   // bar is hidden by default.
   std::string id = test_api()->AddNotification();
@@ -456,7 +464,7 @@
   EXPECT_GT(GetNotificationBarLabel()->bounds().width(), label_width);
 }
 
-TEST_F(NotificationCenterViewTest, FocusClearedAfterNotificationRemoval) {
+TEST_P(NotificationCenterViewTest, FocusClearedAfterNotificationRemoval) {
   test_api()->AddNotification();
   auto id1 = test_api()->AddNotification();
 
@@ -473,7 +481,7 @@
   EXPECT_FALSE(notification_center_view()->GetFocusManager()->GetFocusedView());
 }
 
-TEST_F(NotificationCenterViewTest, ClearAllButtonHeight) {
+TEST_P(NotificationCenterViewTest, ClearAllButtonHeight) {
   std::string id0 = test_api()->AddNotification();
   std::string id1 = test_api()->AddNotification();
   test_api()->ToggleBubble();
@@ -494,7 +502,7 @@
 }
 
 // Tests that the "Clear all" button is not focusable when it is disabled.
-TEST_F(NotificationCenterViewTest, ClearAllNotFocusableWhenDisabled) {
+TEST_P(NotificationCenterViewTest, ClearAllNotFocusableWhenDisabled) {
   // Add a pinned notification and toggle the bubble.
   test_api()->AddPinnedNotification();
   test_api()->ToggleBubble();
@@ -510,7 +518,7 @@
   EXPECT_FALSE(GetNotificationBarClearAllButton()->HasFocus());
 }
 
-TEST_F(NotificationCenterViewTest, StackedNotificationCount) {
+TEST_P(NotificationCenterViewTest, StackedNotificationCount) {
   // There should not be any stacked notifications in the message
   // center with just one notification added.
   test_api()->AddNotification();
@@ -529,7 +537,7 @@
 }
 
 // Test for notification swipe control visibility.
-TEST_F(NotificationCenterViewTest, NotificationPartialSwipe) {
+TEST_P(NotificationCenterViewTest, NotificationPartialSwipe) {
   auto id1 = test_api()->AddNotification();
   test_api()->ToggleBubble();
   auto* view = test_api()->GetNotificationViewForId(id1);
diff --git a/ash/system/notification_center/views/notification_list_view_unittest.cc b/ash/system/notification_center/views/notification_list_view_unittest.cc
index b24f2b68..8b89ef9 100644
--- a/ash/system/notification_center/views/notification_list_view_unittest.cc
+++ b/ash/system/notification_center/views/notification_list_view_unittest.cc
@@ -4,15 +4,17 @@
 
 #include "ash/system/notification_center/views/notification_list_view.h"
 
-#include "ash/system/notification_center/views/ash_notification_view.h"
+#include "ash/constants/ash_features.h"
 #include "ash/system/notification_center/message_center_constants.h"
 #include "ash/system/notification_center/notification_center_test_api.h"
+#include "ash/system/notification_center/views/ash_notification_view.h"
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/test/ash_test_base.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "ui/base/models/image_model.h"
 #include "ui/compositor/layer.h"
@@ -126,15 +128,17 @@
 
 // The base test class, has no params so tests with no params can inherit from
 // this.
-class NotificationListViewTest : public AshTestBase,
-                                 public views::ViewObserver {
+class NotificationListViewTest
+    : public AshTestBase,
+      public views::ViewObserver,
+      public testing::WithParamInterface<
+          /*enable_notification_center_controller=*/bool> {
  public:
-  NotificationListViewTest() = default;
-
-  NotificationListViewTest(const NotificationListViewTest&) = delete;
-  NotificationListViewTest& operator=(const NotificationListViewTest&) = delete;
-
-  ~NotificationListViewTest() override = default;
+  NotificationListViewTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        features::kNotificationCenterController,
+        IsNotificationCenterControllerEnabled());
+  }
 
   void SetUp() override {
     AshTestBase::SetUp();
@@ -158,6 +162,8 @@
 
   NotificationCenterTestApi* test_api() { return test_api_.get(); }
 
+  bool IsNotificationCenterControllerEnabled() const { return GetParam(); }
+
  protected:
   std::string AddNotification(bool pinned = false, bool expandable = false) {
     std::string id = base::NumberToString(id_++);
@@ -252,12 +258,18 @@
   int id_ = 0;
   int size_changed_count_ = 0;
 
+  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<NotificationCenterTestApi> test_api_;
   scoped_refptr<UnifiedSystemTrayModel> model_;
   std::unique_ptr<TestNotificationListView> notification_list_view_;
 };
 
-TEST_F(NotificationListViewTest, Open) {
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    NotificationListViewTest,
+    /*enable_notification_center_controller=*/testing::Bool());
+
+TEST_P(NotificationListViewTest, Open) {
   auto id0 = AddNotification();
   auto id1 = AddNotification();
   auto id2 = AddNotification();
@@ -294,7 +306,7 @@
   EXPECT_LT(0, message_list_view()->GetPreferredSize().height());
 }
 
-TEST_F(NotificationListViewTest, AddNotifications) {
+TEST_P(NotificationListViewTest, AddNotifications) {
   CreateMessageListView();
   EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
 
@@ -332,7 +344,7 @@
   EXPECT_EQ(top_bottom_corner_radius, GetMessageViewAt(1)->bottom_radius());
 }
 
-TEST_F(NotificationListViewTest, RemoveNotification) {
+TEST_P(NotificationListViewTest, RemoveNotification) {
   auto id0 = AddNotification();
   auto id1 = AddNotification();
 
@@ -365,7 +377,7 @@
   EXPECT_EQ(0, message_list_view()->GetPreferredSize().height());
 }
 
-TEST_F(NotificationListViewTest, CollapseOlderNotifications) {
+TEST_P(NotificationListViewTest, CollapseOlderNotifications) {
   AddNotification();
   CreateMessageListView();
   EXPECT_TRUE(GetMessageViewAt(0)->IsExpanded());
@@ -393,7 +405,7 @@
   EXPECT_FALSE(GetMessageViewAt(3)->IsExpanded());
 }
 
-TEST_F(NotificationListViewTest, RemovingNotificationAnimation) {
+TEST_P(NotificationListViewTest, RemovingNotificationAnimation) {
   auto id0 = AddNotification(/*pinned=*/false);
   auto id1 = AddNotification();
   auto id2 = AddNotification();
@@ -432,7 +444,7 @@
 }
 
 // Flaky: https://crbug.com/1292774.
-TEST_F(NotificationListViewTest, DISABLED_ResetAnimation) {
+TEST_P(NotificationListViewTest, DISABLED_ResetAnimation) {
   auto id0 = AddNotification();
   auto id1 = AddNotification();
   CreateMessageListView();
@@ -451,7 +463,7 @@
   EXPECT_EQ(id2, GetMessageViewAt(1)->notification_id());
 }
 
-TEST_F(NotificationListViewTest, KeepManuallyExpanded) {
+TEST_P(NotificationListViewTest, KeepManuallyExpanded) {
   AddNotification();
   AddNotification();
   CreateMessageListView();
@@ -494,7 +506,7 @@
   EXPECT_FALSE(GetMessageViewAt(0)->IsManuallyExpandedOrCollapsed());
 }
 
-TEST_F(NotificationListViewTest, ClearAllWithOnlyVisibleNotifications) {
+TEST_P(NotificationListViewTest, ClearAllWithOnlyVisibleNotifications) {
   AddNotification();
   AddNotification();
   CreateMessageListView();
@@ -536,7 +548,7 @@
   EXPECT_FALSE(IsAnimating());
 }
 
-TEST_F(NotificationListViewTest, ClearAllWithStackingNotifications) {
+TEST_P(NotificationListViewTest, ClearAllWithStackingNotifications) {
   AddNotification();
   AddNotification();
   AddNotification();
@@ -583,7 +595,7 @@
   EXPECT_FALSE(IsAnimating());
 }
 
-TEST_F(NotificationListViewTest, ClearAllClosedInTheMiddle) {
+TEST_P(NotificationListViewTest, ClearAllClosedInTheMiddle) {
   AddNotification();
   AddNotification();
   AddNotification();
@@ -596,7 +608,7 @@
   EXPECT_TRUE(MessageCenter::Get()->GetVisibleNotifications().empty());
 }
 
-TEST_F(NotificationListViewTest, ClearAllInterrupted) {
+TEST_P(NotificationListViewTest, ClearAllInterrupted) {
   AddNotification();
   AddNotification();
   AddNotification();
@@ -610,7 +622,7 @@
   EXPECT_TRUE(MessageCenter::Get()->FindVisibleNotificationById(new_id));
 }
 
-TEST_F(NotificationListViewTest, ClearAllWithPinnedNotifications) {
+TEST_P(NotificationListViewTest, ClearAllWithPinnedNotifications) {
   AddNotification(/*pinned=*/true);
   AddNotification();
   AddNotification();
@@ -621,7 +633,7 @@
   EXPECT_EQ(1u, message_list_view()->children().size());
 }
 
-TEST_F(NotificationListViewTest, ClearAllWithStackingAndPinnedNotifications) {
+TEST_P(NotificationListViewTest, ClearAllWithStackingAndPinnedNotifications) {
   AddNotification(/*pinned=*/true);
   AddNotification(/*pinned=*/true);
   AddNotification();
@@ -637,7 +649,7 @@
 }
 
 // Flaky: https://crbug.com/1292701.
-TEST_F(NotificationListViewTest, DISABLED_UserSwipesAwayNotification) {
+TEST_P(NotificationListViewTest, DISABLED_UserSwipesAwayNotification) {
   // Show message list with two notifications.
   AddNotification();
   auto id1 = AddNotification();
@@ -663,7 +675,7 @@
   EXPECT_FALSE(message_list_view()->IsAnimating());
 }
 
-TEST_F(NotificationListViewTest, InitInSortedOrder) {
+TEST_P(NotificationListViewTest, InitInSortedOrder) {
   // MessageViews should be ordered, from top down: [ id1, id2, id0 ].
   auto id0 = AddNotification(/*pinned=*/true);
   OffsetNotificationTimestamp(id0, 2000 /* milliseconds */);
@@ -678,7 +690,7 @@
   EXPECT_EQ(id0, GetMessageViewAt(0)->notification_id());
 }
 
-TEST_F(NotificationListViewTest, NotificationAddedInSortedOrder) {
+TEST_P(NotificationListViewTest, NotificationAddedInSortedOrder) {
   auto id0 = AddNotification(/*pinned=*/true);
   OffsetNotificationTimestamp(id0, 3000 /* milliseconds */);
   auto id1 = AddNotification();
@@ -704,7 +716,7 @@
   EXPECT_EQ(id3, GetMessageViewAt(0)->notification_id());
 }
 
-TEST_F(NotificationListViewTest, OnChildNotificationViewUpdated) {
+TEST_P(NotificationListViewTest, OnChildNotificationViewUpdated) {
   const std::string source_url = "http://test-url.com";
 
   std::string id0;
@@ -753,7 +765,7 @@
 }
 
 // Tests that preferred size changes upon toggle of expand/collapse.
-TEST_F(NotificationListViewTest, PreferredSizeChangesOnToggle) {
+TEST_P(NotificationListViewTest, PreferredSizeChangesOnToggle) {
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   CreateMessageListView();
@@ -787,7 +799,7 @@
 
 // Tests that expanding a notification while a different notification is
 // expanding is handled gracefully.
-TEST_F(NotificationListViewTest, TwoExpandsInARow) {
+TEST_P(NotificationListViewTest, TwoExpandsInARow) {
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   CreateMessageListView();
@@ -828,7 +840,7 @@
 }
 
 // Tests that collapsing/expanding is reversible.
-TEST_F(NotificationListViewTest, ReverseExpand) {
+TEST_P(NotificationListViewTest, ReverseExpand) {
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   CreateMessageListView();
@@ -856,7 +868,7 @@
 }
 
 // Tests that destroying during a collapse animation does not crash.
-TEST_F(NotificationListViewTest, DestroyMessageListViewDuringCollapse) {
+TEST_P(NotificationListViewTest, DestroyMessageListViewDuringCollapse) {
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   AddNotification(/*pinned=*/false, /*expandable=*/true);
   CreateMessageListView();
@@ -869,7 +881,7 @@
 
 // Tests that closing a notification while its collapse animation is ongoing
 // works properly.
-TEST_F(NotificationListViewTest, RemoveNotificationDuringCollapse) {
+TEST_P(NotificationListViewTest, RemoveNotificationDuringCollapse) {
   auto id1 = AddNotification(/*pinned=*/false, /*expandable=*/true);
   CreateMessageListView();
   auto* message_view = GetMessageViewAt(0);
@@ -895,7 +907,7 @@
 // Tests that expanding a notification at various stages while it is being
 // closed does not result in an animation.
 // TODO(crbug.com/1292775): Test is flaky.
-TEST_F(NotificationListViewTest,
+TEST_P(NotificationListViewTest,
        DISABLED_CollapseDuringCloseResultsInNoCollapseAnimation) {
   auto id1 = AddNotification(/*pinned=*/false, /*expandable=*/true);
   AddNotification(/*pinned=*/false, /*expandable=*/true);
@@ -933,7 +945,7 @@
 // Tests that collapsing a notification while it is being moved automatically
 // completes both animations.
 // TODO(crbug.com/1292816): Test is flaky.
-TEST_F(NotificationListViewTest, DISABLED_CollapseDuringMoveNoAnimation) {
+TEST_P(NotificationListViewTest, DISABLED_CollapseDuringMoveNoAnimation) {
   auto to_be_removed_notification =
       AddNotification(/*pinned=*/false, /*expandable=*/true);
   auto to_be_collapsed_notification =
@@ -969,7 +981,7 @@
 
 // Tests that moving a notification while it is already collapsing completes
 // both animations.
-TEST_F(NotificationListViewTest, MoveDuringCollapseNoAnimation) {
+TEST_P(NotificationListViewTest, MoveDuringCollapseNoAnimation) {
   auto to_be_removed_notification =
       AddNotification(/*pinned=*/false, /*expandable=*/true);
   auto to_be_collapsed_notification =
@@ -999,7 +1011,7 @@
       to_be_collapsed_message_view_container->GetPreferredSize().height());
 }
 
-TEST_F(NotificationListViewTest, SlideNotification) {
+TEST_P(NotificationListViewTest, SlideNotification) {
   // Show message list with four notifications.
   auto id0 = AddNotification();
   auto id1 = AddNotification();
diff --git a/ash/system/time/calendar_event_list_view.cc b/ash/system/time/calendar_event_list_view.cc
index d5c4e56..b0aefa2 100644
--- a/ash/system/time/calendar_event_list_view.cc
+++ b/ash/system/time/calendar_event_list_view.cc
@@ -193,6 +193,20 @@
     gradient_helper_->UpdateGradientMask();
   }
 
+  // If `CalendarEventListItemView` or the join button is focused, do not scroll
+  // to the current or next event. Otherwise `scroll_view_` won't scroll with
+  // the focus change.
+  if (GetFocusManager() && GetFocusManager()->GetFocusedView()) {
+    const auto focused_view_class_name =
+        std::string_view(GetFocusManager()->GetFocusedView()->GetClassName());
+    if (focused_view_class_name ==
+            std::string_view(CalendarEventListItemView::kViewClassName) ||
+        focused_view_class_name ==
+            std::string_view(PillButton::kViewClassName)) {
+      return;
+    }
+  }
+
   const std::optional<base::Time> selected_date =
       calendar_view_controller_->selected_date();
 
diff --git a/ash/system/time/calendar_event_list_view_unittest.cc b/ash/system/time/calendar_event_list_view_unittest.cc
index 71501ba..ea79337 100644
--- a/ash/system/time/calendar_event_list_view_unittest.cc
+++ b/ash/system/time/calendar_event_list_view_unittest.cc
@@ -335,6 +335,53 @@
 }
 
 TEST_P(CalendarViewEventListViewTest,
+       DoNotScrollToCurrentOrNextEventWhenFocused) {
+  // Sets the timezone to GMT. Otherwise in other timezones events can become
+  // multi-day events that will be ignored when calculating index.
+  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
+
+  // Sets today to be a day with many events, so `event_list_view()` is
+  // scrollable.
+  base::Time date;
+  ASSERT_TRUE(base::Time::FromString("23 Nov 2021 08:00 GMT", &date));
+  CreateEventListView(date);
+
+  SetSelectedDate(date);
+
+  // Sets the current time to be a time that event id_9 has started.
+  base::Time current_time;
+  ASSERT_TRUE(base::Time::FromString("23 Nov 2021 02:40 GMT", &current_time));
+  SetFakeNow(current_time);
+  base::subtle::ScopedTimeClockOverrides time_override(
+      &CalendarViewEventListViewTest::FakeTimeNow,
+      /*time_ticks_override=*/nullptr, /*thread_ticks_override=*/nullptr);
+  UpdateEventList();
+  views::test::RunScheduledLayout(event_list_view());
+
+  // The current or next event should be the second event(event id_9).
+  EXPECT_EQ(1, current_or_next_event_index());
+
+  // The top of `scroll_view()` visible rect should be the same with the top of
+  // the current or next event.
+  auto scroll_view_visible_bounds = scroll_view()->GetVisibleBounds();
+  views::View::ConvertRectToScreen(scroll_view(), &scroll_view_visible_bounds);
+  auto current_item_bounds =
+      GetHighlightView(current_or_next_event_index())->GetBoundsInScreen();
+  EXPECT_EQ(scroll_view_visible_bounds.y(), current_item_bounds.y());
+
+  // Focus on first item in `event_list_view()`, and `scroll_view()` should
+  // scroll up along with the focus change.
+  GetHighlightView(0)->RequestFocus();
+  views::test::RunScheduledLayout(event_list_view());
+
+  scroll_view_visible_bounds = scroll_view()->GetVisibleBounds();
+  views::View::ConvertRectToScreen(scroll_view(), &scroll_view_visible_bounds);
+  current_item_bounds =
+      GetHighlightView(current_or_next_event_index())->GetBoundsInScreen();
+  EXPECT_LT(scroll_view_visible_bounds.y(), current_item_bounds.y());
+}
+
+TEST_P(CalendarViewEventListViewTest,
        ScrollToCurrentOrNextEvent_WithMultiDayEvents) {
   // Sets the timezone to HST. The first two events become multi-day events, and
   // will be ignored when calculating index.
diff --git a/ash/system/time/calendar_utils.cc b/ash/system/time/calendar_utils.cc
index b074240..5083dcd7 100644
--- a/ash/system/time/calendar_utils.cc
+++ b/ash/system/time/calendar_utils.cc
@@ -26,8 +26,7 @@
 namespace calendar_utils {
 
 bool IsForGlanceablesV2() {
-  return features::AreGlanceablesV2Enabled() &&
-         features::IsGlanceablesV2CalendarViewEnabled();
+  return features::IsGlanceablesV2CalendarViewEnabled();
 }
 
 bool IsToday(const base::Time selected_date) {
diff --git a/ash/webui/common/mojom/sea_pen.mojom b/ash/webui/common/mojom/sea_pen.mojom
index 1f118b4..176c927 100644
--- a/ash/webui/common/mojom/sea_pen.mojom
+++ b/ash/webui/common/mojom/sea_pen.mojom
@@ -7,12 +7,21 @@
 import "mojo/public/mojom/base/file_path.mojom";
 import "url/mojom/url.mojom";
 
+// Generated by sea_pen_client_generator.py, do not edit.
+// TODO(b/318565684): Move generated code to a separate file.
+
 // The unique ID to identify a template.
 enum SeaPenTemplateId {
   kFlower = 0,
   kMineral,
   kLandscape,
   kScifi,
+  kArt,
+  kCharacters,
+  kTerrain,
+  kCurious,
+  kDreamscapes,
+  kTranslucent,
 };
 
 // A variable in the template text that can be mapped to multiple option values.
@@ -25,11 +34,26 @@
   kLandscapeLighting,
   kScifiFeature,
   kScifiColor,
+  kArtFeature,
+  kArtMovement,
+  kCharactersColor,
+  kCharactersSubjects,
+  kCharactersBackground,
+  kTerrainFeature,
+  kTerrainColor,
+  kCuriousSubject,
+  kCuriousFeature,
+  kCuriousColor,
+  kDreamscapesObject,
+  kDreamscapesMaterial,
+  kDreamscapesColors,
+  kTranslucentItem,
+  kTranslucentColor,
 };
 
 // The valid option values of each chip.
 enum SeaPenTemplateOption {
-  // Flower Type
+  // <flower_type>
   kFlowerTypeRose = 0,
   kFlowerTypeCallaLily,
   kFlowerTypeWindflower,
@@ -40,7 +64,7 @@
   kFlowerTypeRanunculus,
   kFlowerTypeDaisy,
   kFlowerTypeHydrangeas,
-  // Flower Color
+  // <flower_color>
   kFlowerColorPink,
   kFlowerColorPurple,
   kFlowerColorBlue,
@@ -49,7 +73,7 @@
   kFlowerColorYellow,
   kFlowerColorGreen,
   kFlowerColorRed,
-  // Mineral Name
+  // <mineral_name>
   kMineralNameAgate,
   kMineralNameAmethyst,
   kMineralNameAquamarine,
@@ -71,7 +95,7 @@
   kMineralNameSapphire,
   kMineralNameQuartz,
   kMineralNameTourmaline,
-  // Mineral Color
+  // <mineral_color>
   kMineralColorWarm,
   kMineralColorCool,
   kMineralColorNeutral,
@@ -83,7 +107,7 @@
   kMineralColorNeon,
   kMineralColorTonal,
   kMineralColorGray,
-  // Landscape Biome
+  // <landscape_biome>
   kLandscapeBiomeTaiga,
   kLandscapeBiomeDesert,
   kLandscapeBiomeRainforest,
@@ -93,7 +117,7 @@
   kLandscapeBiomeSwamp,
   kLandscapeBiomeGrassland,
   kLandscapeBiomeForest,
-  // Landscape Lighting
+  // <landscape_lighting>
   kLandscapeLightingDiffuse,
   kLandscapeLightingNorthernLights,
   kLandscapeLightingSunRays,
@@ -101,7 +125,7 @@
   kLandscapeLightingEarlyMorning,
   kLandscapeLightingBlueHour,
   kLandscapeLightingMidday,
-  // Scifi Feature
+  // <scifi_feature>
   kScifiFeatureStreet,
   kScifiFeatureSkyline,
   kScifiFeatureSwamp,
@@ -115,14 +139,319 @@
   kScifiFeatureSmallTown,
   kScifiFeatureFarm,
   kScifiFeatureUnderwater,
-  // Scifi Color
+  // <scifi_color>
   kScifiColorEarthy,
   kScifiColorVibrant,
   kScifiColorSilver,
   kScifiColorEerie,
   kScifiColorComplementary,
   kScifiColorNeutral,
+  // <art_feature>
+  kArtFeatureCanyon,
+  kArtFeatureMountain,
+  kArtFeatureBeach,
+  kArtFeatureCave,
+  kArtFeatureCliff,
+  kArtFeatureForest,
+  kArtFeatureGlacier,
+  kArtFeatureIsland,
+  kArtFeatureJungle,
+  kArtFeatureLake,
+  kArtFeatureMeadow,
+  kArtFeatureOcean,
+  kArtFeatureRiver,
+  kArtFeatureDune,
+  kArtFeatureSwamp,
+  kArtFeatureValley,
+  kArtFeatureWaterfall,
+  kArtFeatureField,
+  kArtFeatureCityscape,
+  kArtFeatureVillage,
+  // <art_movement>
+  kArtMovementAvantGarde,
+  kArtMovementRealism,
+  kArtMovementExpressionism,
+  kArtMovementImpressionism,
+  kArtMovementPostImpressionism,
+  kArtMovementArtNouveau,
+  kArtMovementBaroque,
+  kArtMovementBauhaus,
+  kArtMovementClassicism,
+  kArtMovementWatercolor,
+  kArtMovementAbstract,
+  kArtMovementPointillism,
+  kArtMovementGraphicDesign,
+  kArtMovementModernArt,
+  // <characters_color>
+  kCharactersColorYellow,
+  kCharactersColorPink,
+  kCharactersColorRed,
+  kCharactersColorBlue,
+  kCharactersColorIndigo,
+  kCharactersColorGreen,
+  kCharactersColorEmerald,
+  kCharactersColorTeal,
+  kCharactersColorCyan,
+  kCharactersColorPurple,
+  kCharactersColorBrown,
+  kCharactersColorGold,
+  kCharactersColorBurntOrange,
+  kCharactersColorRust,
+  kCharactersColorOlive,
+  kCharactersColorGray,
+  kCharactersColorViolet,
+  kCharactersColorWhite,
+  kCharactersColorBeige,
+  kCharactersColorIvory,
+  kCharactersColorCream,
+  kCharactersColorMagenta,
+  kCharactersColorLimeGreen,
+  kCharactersColorFuschia,
+  kCharactersColorElectricBlue,
+  kCharactersColorHotPink,
+  kCharactersColorNeonGreen,
+  kCharactersColorSkyBlue,
+  kCharactersColorElectricPurple,
+  kCharactersColorFireEngineRed,
+  kCharactersColorNeonPink,
+  kCharactersColorChartreuse,
+  kCharactersColorCobalt,
+  kCharactersColorLemonYellow,
+  kCharactersColorCoralPink,
+  kCharactersColorVibrantViolet,
+  kCharactersColorPeacockBlue,
+  // <characters_subjects>
+  kCharactersSubjectsLemons,
+  kCharactersSubjectsFlowers,
+  kCharactersSubjectsApples,
+  kCharactersSubjectsCherries,
+  kCharactersSubjectsOranges,
+  kCharactersSubjectsPineapples,
+  kCharactersSubjectsStrawberries,
+  kCharactersSubjectsWatermelons,
+  kCharactersSubjectsPotatoes,
+  kCharactersSubjectsSushi,
+  kCharactersSubjectsBaconAndEggs,
+  kCharactersSubjectsPizza,
+  kCharactersSubjectsHotDogs,
+  kCharactersSubjectsHamburgers,
+  kCharactersSubjectsRamen,
+  kCharactersSubjectsTacos,
+  kCharactersSubjectsBunnies,
+  kCharactersSubjectsCats,
+  kCharactersSubjectsDogs,
+  kCharactersSubjectsKoalas,
+  kCharactersSubjectsPandas,
+  kCharactersSubjectsPenguins,
+  kCharactersSubjectsPigs,
+  kCharactersSubjectsSloths,
+  kCharactersSubjectsPonies,
+  kCharactersSubjectsElephants,
+  kCharactersSubjectsFoxes,
+  kCharactersSubjectsOwls,
+  kCharactersSubjectsCrabs,
+  kCharactersSubjectsBees,
+  kCharactersSubjectsButterflies,
+  kCharactersSubjectsBicycles,
+  kCharactersSubjectsBoats,
+  kCharactersSubjectsBooks,
+  kCharactersSubjectsCutlery,
+  kCharactersSubjectsUmbrellas,
+  kCharactersSubjectsInstruments,
+  // <characters_background>
+  kCharactersBackgroundPurple,
+  kCharactersBackgroundBlue,
+  kCharactersBackgroundIndigo,
+  kCharactersBackgroundGreen,
+  kCharactersBackgroundEmerald,
+  kCharactersBackgroundTeal,
+  kCharactersBackgroundCyan,
+  kCharactersBackgroundBrown,
+  kCharactersBackgroundGold,
+  kCharactersBackgroundRed,
+  kCharactersBackgroundBurntOrange,
+  kCharactersBackgroundRust,
+  kCharactersBackgroundOlive,
+  kCharactersBackgroundPink,
+  kCharactersBackgroundGray,
+  kCharactersBackgroundYellow,
+  kCharactersBackgroundViolet,
+  kCharactersBackgroundWhite,
+  kCharactersBackgroundBeige,
+  kCharactersBackgroundIvory,
+  kCharactersBackgroundCream,
+  kCharactersBackgroundMagenta,
+  kCharactersBackgroundLimeGreen,
+  kCharactersBackgroundFuschia,
+  kCharactersBackgroundElectricBlue,
+  kCharactersBackgroundHotPink,
+  kCharactersBackgroundNeonGreen,
+  kCharactersBackgroundSkyBlue,
+  kCharactersBackgroundElectricPurple,
+  kCharactersBackgroundFireEngineRed,
+  kCharactersBackgroundNeonPink,
+  kCharactersBackgroundChartreuse,
+  kCharactersBackgroundCobalt,
+  kCharactersBackgroundLemonYellow,
+  kCharactersBackgroundCoralPink,
+  kCharactersBackgroundVibrantViolet,
+  kCharactersBackgroundPeacockBlue,
+  // <terrain_feature>
+  kTerrainFeatureSaltLake,
+  kTerrainFeatureRiver,
+  kTerrainFeatureNorthernLights,
+  kTerrainFeatureWhiteDunes,
+  kTerrainFeatureClayHills,
+  kTerrainFeatureSandyLagoons,
+  kTerrainFeatureMountains,
+  kTerrainFeatureBioluminescentBeach,
+  kTerrainFeatureFireflyForest,
+  kTerrainFeatureSandDunes,
+  // <terrain_color>
+  kTerrainColorPink,
+  kTerrainColorTeal,
+  kTerrainColorWhite,
+  kTerrainColorPurple,
+  kTerrainColorBlue,
+  kTerrainColorYellow,
+  kTerrainColorMaroonPink,
+  kTerrainColorBluePurple,
+  kTerrainColorPinkYellow,
+  kTerrainColorBluePink,
+  // <curious_subject>
+  kCuriousSubjectCherryBlossoms,
+  kCuriousSubjectJasmineFlowers,
+  kCuriousSubjectDaisies,
+  kCuriousSubjectTulips,
+  kCuriousSubjectCarnations,
+  kCuriousSubjectDaffodils,
+  kCuriousSubjectForgetMeNots,
+  kCuriousSubjectSunflowers,
+  kCuriousSubjectBougainvilleas,
+  kCuriousSubjectAirPlants,
+  kCuriousSubjectSucculents,
+  // <curious_feature>
+  kCuriousFeatureAlpineLake,
+  kCuriousFeatureGalaxy,
+  kCuriousFeatureSandDunes,
+  kCuriousFeatureSwamp,
+  kCuriousFeatureBeach,
+  kCuriousFeatureMountains,
+  kCuriousFeatureRiver,
+  kCuriousFeatureWaterfall,
+  // <curious_color>
+  kCuriousColorBlue,
+  kCuriousColorRed,
+  kCuriousColorYellow,
+  kCuriousColorGreen,
+  kCuriousColorPurple,
+  kCuriousColorOrange,
+  kCuriousColorPink,
+  kCuriousColorBrown,
+  kCuriousColorBlack,
+  kCuriousColorTurquoise,
+  kCuriousColorMagenta,
+  kCuriousColorLavender,
+  kCuriousColorMaroon,
+  kCuriousColorNavy,
+  kCuriousColorOlive,
+  kCuriousColorCoral,
+  kCuriousColorCream,
+  kCuriousColorIndigo,
+  kCuriousColorFuchsia,
+  // <dreamscapes_object>
+  kDreamscapesObjectBicycle,
+  kDreamscapesObjectCastle,
+  kDreamscapesObjectBuilding,
+  kDreamscapesObjectBoat,
+  kDreamscapesObjectLamp,
+  kDreamscapesObjectTable,
+  kDreamscapesObjectBridge,
+  kDreamscapesObjectLighthouse,
+  kDreamscapesObjectPagoda,
+  kDreamscapesObjectPalace,
+  kDreamscapesObjectTower,
+  kDreamscapesObjectUfo,
+  // <dreamscapes_material>
+  kDreamscapesMaterialFlowers,
+  kDreamscapesMaterialSilk,
+  kDreamscapesMaterialFelt,
+  kDreamscapesMaterialBurlap,
+  kDreamscapesMaterialChiffon,
+  kDreamscapesMaterialCotton,
+  kDreamscapesMaterialFur,
+  kDreamscapesMaterialLace,
+  kDreamscapesMaterialLinen,
+  kDreamscapesMaterialOrganza,
+  kDreamscapesMaterialTulle,
+  kDreamscapesMaterialWool,
+  kDreamscapesMaterialYarn,
+  kDreamscapesMaterialFleece,
+  kDreamscapesMaterialClay,
+  kDreamscapesMaterialStone,
+  kDreamscapesMaterialWood,
+  kDreamscapesMaterialAmethyst,
+  kDreamscapesMaterialLapisLuzuli,
+  kDreamscapesMaterialObsidian,
+  kDreamscapesMaterialOpal,
+  kDreamscapesMaterialSapphire,
+  // <dreamscapes_colors>
+  kDreamscapesColorsPinkPurple,
+  kDreamscapesColorsCoralTan,
+  kDreamscapesColorsCreamOrange,
+  kDreamscapesColorsBlueIndigo,
+  kDreamscapesColorsGreenTeal,
+  kDreamscapesColorsBurgundyMaroon,
+  kDreamscapesColorsYellowTeal,
+  // <translucent_item>
+  kTranslucentItemApple,
+  kTranslucentItemAzalea,
+  kTranslucentItemBegonia,
+  kTranslucentItemBluebell,
+  kTranslucentItemCherryBlossom,
+  kTranslucentItemChrysanthemum,
+  kTranslucentItemClemati,
+  kTranslucentItemDaffodil,
+  kTranslucentItemDaisy,
+  kTranslucentItemDandelion,
+  kTranslucentItemRose,
+  kTranslucentItemDogwood,
+  kTranslucentItemHibiscus,
+  kTranslucentItemHydrangea,
+  kTranslucentItemLeaf,
+  kTranslucentItemLily,
+  kTranslucentItemPansy,
+  kTranslucentItemPear,
+  kTranslucentItemPeony,
+  kTranslucentItemPhilodendron,
+  kTranslucentItemPoppy,
+  kTranslucentItemSunflower,
+  kTranslucentItemPea,
+  kTranslucentItemTulip,
+  kTranslucentItemButterfly,
+  kTranslucentItemDragonfly,
+  // <translucent_color>
+  kTranslucentColorPink,
+  kTranslucentColorBlue,
+  kTranslucentColorIndigo,
+  kTranslucentColorGreen,
+  kTranslucentColorEmerald,
+  kTranslucentColorTeal,
+  kTranslucentColorCyan,
+  kTranslucentColorPurple,
+  kTranslucentColorGold,
+  kTranslucentColorRed,
+  kTranslucentColorRust,
+  kTranslucentColorOlive,
+  kTranslucentColorGray,
+  kTranslucentColorYellow,
+  kTranslucentColorViolet,
+  kTranslucentColorIvory,
+  kTranslucentColorMagenta,
+  kTranslucentColorPeach,
+  kTranslucentColorBlack,
 };
+// End of generated code.
 
 // Encapsulates metadata for a thumbnail image, which can be displayed as an
 // image tile in the SeaPen search result page.
diff --git a/ash/webui/common/resources/sea_pen/constants.ts b/ash/webui/common/resources/sea_pen/constants.ts
index 1672787..a12b3d1 100644
--- a/ash/webui/common/resources/sea_pen/constants.ts
+++ b/ash/webui/common/resources/sea_pen/constants.ts
@@ -36,10 +36,12 @@
 }
 
 export function getSeaPenTemplates(): SeaPenTemplate[] {
+  // Generated by sea_pen_client_generator.py, do not edit.
+  // TODO(b/318565684): Move generated code to a separate file.
   const templates = [
     {
       id: SeaPenTemplateId.kFlower.toString(),
-      title: 'Airbrushed',
+      title: 'Airbrush',
       text: `A radiant <${SeaPenTemplateChip.kFlowerColor}> <${
           SeaPenTemplateChip.kFlowerType}> in bloom`,
       preview: [{
@@ -47,51 +49,6 @@
       }],
       options: new Map([
         [
-          SeaPenTemplateChip.kFlowerType,
-          [
-            {
-              value: SeaPenTemplateOption.kFlowerTypeRose,
-              translation: 'rose',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeCallaLily,
-              translation: 'calla lily',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeWindflower,
-              translation: 'windflower',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeTulip,
-              translation: 'tulip',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeLilyOfTheValley,
-              translation: 'lily of the valley',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeBirdOfParadise,
-              translation: 'bird-of-paradise flower',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeOrchid,
-              translation: 'orchid',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeRanunculus,
-              translation: 'ranunculus',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeDaisy,
-              translation: 'daisy',
-            },
-            {
-              value: SeaPenTemplateOption.kFlowerTypeHydrangeas,
-              translation: 'hydrangeas',
-            },
-          ],
-        ],
-        [
           SeaPenTemplateChip.kFlowerColor,
           [
             {
@@ -128,6 +85,51 @@
             },
           ],
         ],
+        [
+          SeaPenTemplateChip.kFlowerType,
+          [
+            {
+              value: SeaPenTemplateOption.kFlowerTypeRose,
+              translation: 'rose',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeCallaLily,
+              translation: 'calla lily',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeWindflower,
+              translation: 'windflower',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeTulip,
+              translation: 'tulip',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeLilyOfTheValley,
+              translation: 'lily of the valley',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeBirdOfParadise,
+              translation: 'bird of paradise',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeOrchid,
+              translation: 'orchid',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeRanunculus,
+              translation: 'ranunculus',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeDaisy,
+              translation: 'daisy',
+            },
+            {
+              value: SeaPenTemplateOption.kFlowerTypeHydrangeas,
+              translation: 'hydrangeas',
+            },
+          ],
+        ],
       ]),
     },
     {
@@ -366,7 +368,7 @@
     },
     {
       id: SeaPenTemplateId.kScifi.toString(),
-      title: 'Sci-fi',
+      title: 'Scifi',
       text: `Otherworldly <${SeaPenTemplateChip.kScifiFeature}> in <${
           SeaPenTemplateChip.kScifiColor}> colors`,
       preview: [{
@@ -390,7 +392,7 @@
             },
             {
               value: SeaPenTemplateOption.kScifiFeatureTransport,
-              translation: 'transport hub',
+              translation: 'transport',
             },
             {
               value: SeaPenTemplateOption.kScifiFeatureBusStop,
@@ -461,7 +463,1308 @@
         ],
       ]),
     },
+    {
+      id: SeaPenTemplateId.kArt.toString(),
+      title: 'Classic Art',
+      text: `A painting of a <${SeaPenTemplateChip.kArtFeature}> in the <${
+          SeaPenTemplateChip.kArtMovement}> style`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kArtFeature,
+          [
+            {
+              value: SeaPenTemplateOption.kArtFeatureCanyon,
+              translation: 'canyon',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureMountain,
+              translation: 'mountain',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureBeach,
+              translation: 'beach',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCave,
+              translation: 'cave',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCliff,
+              translation: 'cliff',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureForest,
+              translation: 'forest',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureGlacier,
+              translation: 'glacier',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureIsland,
+              translation: 'island',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureJungle,
+              translation: 'jungle',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureLake,
+              translation: 'lake',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureMeadow,
+              translation: 'meadow',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureOcean,
+              translation: 'ocean',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureRiver,
+              translation: 'river',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureDune,
+              translation: 'dune',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureSwamp,
+              translation: 'swamp',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureValley,
+              translation: 'valley',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureWaterfall,
+              translation: 'waterfall',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureField,
+              translation: 'field',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureCityscape,
+              translation: 'cityscape',
+            },
+            {
+              value: SeaPenTemplateOption.kArtFeatureVillage,
+              translation: 'village',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kArtMovement,
+          [
+            {
+              value: SeaPenTemplateOption.kArtMovementAvantGarde,
+              translation: 'avant garde',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementRealism,
+              translation: 'realism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementExpressionism,
+              translation: 'expressionism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementImpressionism,
+              translation: 'impressionism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementPostImpressionism,
+              translation: 'post impressionism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementArtNouveau,
+              translation: 'art nouveau',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementBaroque,
+              translation: 'baroque',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementBauhaus,
+              translation: 'bauhaus',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementClassicism,
+              translation: 'classicism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementWatercolor,
+              translation: 'watercolor',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementAbstract,
+              translation: 'abstract',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementPointillism,
+              translation: 'pointillism',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementGraphicDesign,
+              translation: 'graphic design',
+            },
+            {
+              value: SeaPenTemplateOption.kArtMovementModernArt,
+              translation: 'modern art',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
+      id: SeaPenTemplateId.kCharacters.toString(),
+      title: 'Characters',
+      text: `<${SeaPenTemplateChip.kCharactersColor}> <${
+          SeaPenTemplateChip.kCharactersSubjects}> on a <${
+          SeaPenTemplateChip.kCharactersBackground}> background`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kCharactersColor,
+          [
+            {
+              value: SeaPenTemplateOption.kCharactersColorYellow,
+              translation: 'yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorPink,
+              translation: 'pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorRed,
+              translation: 'red',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorBlue,
+              translation: 'blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorIndigo,
+              translation: 'indigo',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorGreen,
+              translation: 'green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorEmerald,
+              translation: 'emerald',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorTeal,
+              translation: 'teal',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorCyan,
+              translation: 'cyan',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorPurple,
+              translation: 'purple',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorBrown,
+              translation: 'brown',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorGold,
+              translation: 'gold',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorBurntOrange,
+              translation: 'burnt orange',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorRust,
+              translation: 'rust',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorOlive,
+              translation: 'olive',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorGray,
+              translation: 'gray',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorViolet,
+              translation: 'violet',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorWhite,
+              translation: 'white',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorBeige,
+              translation: 'beige',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorIvory,
+              translation: 'ivory',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorCream,
+              translation: 'cream',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorMagenta,
+              translation: 'magenta',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorLimeGreen,
+              translation: 'lime green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorFuschia,
+              translation: 'fuschia',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorElectricBlue,
+              translation: 'electric blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorHotPink,
+              translation: 'hot pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorNeonGreen,
+              translation: 'neon green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorSkyBlue,
+              translation: 'sky blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorElectricPurple,
+              translation: 'electric purple',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorFireEngineRed,
+              translation: 'fire engine red',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorNeonPink,
+              translation: 'neon pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorChartreuse,
+              translation: 'chartreuse',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorCobalt,
+              translation: 'cobalt',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorLemonYellow,
+              translation: 'lemon yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorCoralPink,
+              translation: 'coral pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorVibrantViolet,
+              translation: 'vibrant violet',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersColorPeacockBlue,
+              translation: 'peacock blue',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kCharactersSubjects,
+          [
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsLemons,
+              translation: 'lemons',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsFlowers,
+              translation: 'flowers',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsApples,
+              translation: 'apples',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsCherries,
+              translation: 'cherries',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsOranges,
+              translation: 'oranges',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPineapples,
+              translation: 'pineapples',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsStrawberries,
+              translation: 'strawberries',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsWatermelons,
+              translation: 'watermelons',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPotatoes,
+              translation: 'potatoes',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsSushi,
+              translation: 'sushi',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBaconAndEggs,
+              translation: 'bacon and eggs',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPizza,
+              translation: 'pizza',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsHotDogs,
+              translation: 'hot dogs',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsHamburgers,
+              translation: 'hamburgers',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsRamen,
+              translation: 'ramen',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsTacos,
+              translation: 'tacos',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBunnies,
+              translation: 'bunnies',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsCats,
+              translation: 'cats',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsDogs,
+              translation: 'dogs',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsKoalas,
+              translation: 'koalas',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPandas,
+              translation: 'pandas',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPenguins,
+              translation: 'penguins',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPigs,
+              translation: 'pigs',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsSloths,
+              translation: 'sloths',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsPonies,
+              translation: 'ponies',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsElephants,
+              translation: 'elephants',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsFoxes,
+              translation: 'foxes',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsOwls,
+              translation: 'owls',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsCrabs,
+              translation: 'crabs',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBees,
+              translation: 'bees',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsButterflies,
+              translation: 'butterflies',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBicycles,
+              translation: 'bicycles',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBoats,
+              translation: 'boats',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsBooks,
+              translation: 'books',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsCutlery,
+              translation: 'cutlery',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsUmbrellas,
+              translation: 'umbrellas',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersSubjectsInstruments,
+              translation: 'instruments',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kCharactersBackground,
+          [
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundPurple,
+              translation: 'purple',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundBlue,
+              translation: 'blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundIndigo,
+              translation: 'indigo',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundGreen,
+              translation: 'green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundEmerald,
+              translation: 'emerald',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundTeal,
+              translation: 'teal',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundCyan,
+              translation: 'cyan',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundBrown,
+              translation: 'brown',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundGold,
+              translation: 'gold',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundRed,
+              translation: 'red',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundBurntOrange,
+              translation: 'burnt orange',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundRust,
+              translation: 'rust',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundOlive,
+              translation: 'olive',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundPink,
+              translation: 'pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundGray,
+              translation: 'gray',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundYellow,
+              translation: 'yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundViolet,
+              translation: 'violet',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundWhite,
+              translation: 'white',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundBeige,
+              translation: 'beige',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundIvory,
+              translation: 'ivory',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundCream,
+              translation: 'cream',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundMagenta,
+              translation: 'magenta',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundLimeGreen,
+              translation: 'lime green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundFuschia,
+              translation: 'fuschia',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundElectricBlue,
+              translation: 'electric blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundHotPink,
+              translation: 'hot pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundNeonGreen,
+              translation: 'neon green',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundSkyBlue,
+              translation: 'sky blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundElectricPurple,
+              translation: 'electric purple',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundFireEngineRed,
+              translation: 'fire engine red',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundNeonPink,
+              translation: 'neon pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundChartreuse,
+              translation: 'chartreuse',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundCobalt,
+              translation: 'cobalt',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundLemonYellow,
+              translation: 'lemon yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundCoralPink,
+              translation: 'coral pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundVibrantViolet,
+              translation: 'vibrant violet',
+            },
+            {
+              value: SeaPenTemplateOption.kCharactersBackgroundPeacockBlue,
+              translation: 'peacock blue',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
+      id: SeaPenTemplateId.kTerrain.toString(),
+      title: 'Terrain',
+      text: `<${SeaPenTemplateChip.kTerrainFeature}> in shades of <${
+          SeaPenTemplateChip.kTerrainColor}>`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kTerrainFeature,
+          [
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureSaltLake,
+              translation: 'salt lake',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureRiver,
+              translation: 'river',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureNorthernLights,
+              translation: 'northern lights',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureWhiteDunes,
+              translation: 'white dunes',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureClayHills,
+              translation: 'clay hills',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureSandyLagoons,
+              translation: 'sandy lagoons',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureMountains,
+              translation: 'mountains',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureBioluminescentBeach,
+              translation: 'bioluminescent beach',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureFireflyForest,
+              translation: 'firefly forest',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainFeatureSandDunes,
+              translation: 'sand dunes',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kTerrainColor,
+          [
+            {
+              value: SeaPenTemplateOption.kTerrainColorPink,
+              translation: 'pink',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorTeal,
+              translation: 'teal',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorWhite,
+              translation: 'white',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorPurple,
+              translation: 'purple',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBlue,
+              translation: 'blue',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorYellow,
+              translation: 'yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorMaroonPink,
+              translation: 'maroon pink',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBluePurple,
+              translation: 'blue purple',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorPinkYellow,
+              translation: 'pink yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kTerrainColorBluePink,
+              translation: 'blue pink',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
+      id: SeaPenTemplateId.kCurious.toString(),
+      title: 'Curious World',
+      text: `A <${SeaPenTemplateChip.kCuriousColor}> <${
+          SeaPenTemplateChip.kCuriousFeature}> with <${
+          SeaPenTemplateChip.kCuriousSubject}>`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kCuriousColor,
+          [
+            {
+              value: SeaPenTemplateOption.kCuriousColorBlue,
+              translation: 'blue',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorRed,
+              translation: 'red',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorYellow,
+              translation: 'yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorGreen,
+              translation: 'green',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorPurple,
+              translation: 'purple',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorOrange,
+              translation: 'orange',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorPink,
+              translation: 'pink',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorBrown,
+              translation: 'brown',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorBlack,
+              translation: 'black',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorTurquoise,
+              translation: 'turquoise',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorMagenta,
+              translation: 'magenta',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorLavender,
+              translation: 'lavender',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorMaroon,
+              translation: 'maroon',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorNavy,
+              translation: 'navy',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorOlive,
+              translation: 'olive',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorCoral,
+              translation: 'coral',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorCream,
+              translation: 'cream',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorIndigo,
+              translation: 'indigo',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousColorFuchsia,
+              translation: 'fuchsia',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kCuriousFeature,
+          [
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureAlpineLake,
+              translation: 'alpine lake',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureGalaxy,
+              translation: 'galaxy',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureSandDunes,
+              translation: 'sand dunes',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureSwamp,
+              translation: 'swamp',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureBeach,
+              translation: 'beach',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureMountains,
+              translation: 'mountains',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureRiver,
+              translation: 'river',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousFeatureWaterfall,
+              translation: 'waterfall',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kCuriousSubject,
+          [
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectCherryBlossoms,
+              translation: 'cherry blossoms',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectJasmineFlowers,
+              translation: 'jasmine flowers',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectDaisies,
+              translation: 'daisies',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectTulips,
+              translation: 'tulips',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectCarnations,
+              translation: 'carnations',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectDaffodils,
+              translation: 'daffodils',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectForgetMeNots,
+              translation: 'forget me nots',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectSunflowers,
+              translation: 'sunflowers',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectBougainvilleas,
+              translation: 'bougainvilleas',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectAirPlants,
+              translation: 'air plants',
+            },
+            {
+              value: SeaPenTemplateOption.kCuriousSubjectSucculents,
+              translation: 'succulents',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
+      id: SeaPenTemplateId.kDreamscapes.toString(),
+      title: 'Dreamscapes',
+      text: `A surreal <${SeaPenTemplateChip.kDreamscapesObject}> made of <${
+          SeaPenTemplateChip.kDreamscapesMaterial}> in <${
+          SeaPenTemplateChip.kDreamscapesColors}>`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kDreamscapesObject,
+          [
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectBicycle,
+              translation: 'bicycle',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectCastle,
+              translation: 'castle',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectBuilding,
+              translation: 'building',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectBoat,
+              translation: 'boat',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectLamp,
+              translation: 'lamp',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectTable,
+              translation: 'table',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectBridge,
+              translation: 'bridge',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectLighthouse,
+              translation: 'lighthouse',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectPagoda,
+              translation: 'pagoda',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectPalace,
+              translation: 'palace',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectTower,
+              translation: 'tower',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesObjectUfo,
+              translation: 'UFO',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kDreamscapesMaterial,
+          [
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialFlowers,
+              translation: 'flowers',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialSilk,
+              translation: 'silk',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialFelt,
+              translation: 'felt',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialBurlap,
+              translation: 'burlap',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialChiffon,
+              translation: 'chiffon',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialCotton,
+              translation: 'cotton',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialFur,
+              translation: 'fur',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialLace,
+              translation: 'lace',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialLinen,
+              translation: 'linen',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialOrganza,
+              translation: 'organza',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialTulle,
+              translation: 'tulle',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialWool,
+              translation: 'wool',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialYarn,
+              translation: 'yarn',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialFleece,
+              translation: 'fleece',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialClay,
+              translation: 'clay',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialStone,
+              translation: 'stone',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialWood,
+              translation: 'wood',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialAmethyst,
+              translation: 'amethyst',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialLapisLuzuli,
+              translation: 'lapis luzuli',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialObsidian,
+              translation: 'obsidian',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialOpal,
+              translation: 'opal',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesMaterialSapphire,
+              translation: 'sapphire',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kDreamscapesColors,
+          [
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsPinkPurple,
+              translation: 'pink purple',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsCoralTan,
+              translation: 'coral tan',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsCreamOrange,
+              translation: 'cream orange',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsBlueIndigo,
+              translation: 'blue indigo',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsGreenTeal,
+              translation: 'green teal',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsBurgundyMaroon,
+              translation: 'burgundy maroon',
+            },
+            {
+              value: SeaPenTemplateOption.kDreamscapesColorsYellowTeal,
+              translation: 'yellow teal',
+            },
+          ],
+        ],
+      ]),
+    },
+    {
+      id: SeaPenTemplateId.kTranslucent.toString(),
+      title: 'Translucent',
+      text: `Translucent <${SeaPenTemplateChip.kTranslucentItem}> in <${
+          SeaPenTemplateChip.kTranslucentColor}>`,
+      preview: [{
+        url: 'chrome://personalization/images/sea_pen_tile.svg',
+      }],
+      options: new Map([
+        [
+          SeaPenTemplateChip.kTranslucentItem,
+          [
+            {
+              value: SeaPenTemplateOption.kTranslucentItemApple,
+              translation: 'apple',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemAzalea,
+              translation: 'azalea',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemBegonia,
+              translation: 'begonia',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemBluebell,
+              translation: 'bluebell',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemCherryBlossom,
+              translation: 'cherry blossom',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemChrysanthemum,
+              translation: 'chrysanthemum',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemClemati,
+              translation: 'clemati',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemDaffodil,
+              translation: 'daffodil',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemDaisy,
+              translation: 'daisy',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemDandelion,
+              translation: 'dandelion',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemRose,
+              translation: 'rose',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemDogwood,
+              translation: 'dogwood',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemHibiscus,
+              translation: 'hibiscus',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemHydrangea,
+              translation: 'hydrangea',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemLeaf,
+              translation: 'leaf',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemLily,
+              translation: 'lily',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPansy,
+              translation: 'pansy',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPear,
+              translation: 'pear',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPeony,
+              translation: 'peony',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPhilodendron,
+              translation: 'philodendron',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPoppy,
+              translation: 'poppy',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemSunflower,
+              translation: 'sunflower',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemPea,
+              translation: 'pea',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemTulip,
+              translation: 'tulip',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemButterfly,
+              translation: 'butterfly',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentItemDragonfly,
+              translation: 'dragonfly',
+            },
+          ],
+        ],
+        [
+          SeaPenTemplateChip.kTranslucentColor,
+          [
+            {
+              value: SeaPenTemplateOption.kTranslucentColorPink,
+              translation: 'pink',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorBlue,
+              translation: 'blue',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorIndigo,
+              translation: 'indigo',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorGreen,
+              translation: 'green',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorEmerald,
+              translation: 'emerald',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorTeal,
+              translation: 'teal',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorCyan,
+              translation: 'cyan',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorPurple,
+              translation: 'purple',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorGold,
+              translation: 'gold',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorRed,
+              translation: 'red',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorRust,
+              translation: 'rust',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorOlive,
+              translation: 'olive',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorGray,
+              translation: 'gray',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorYellow,
+              translation: 'yellow',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorViolet,
+              translation: 'violet',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorIvory,
+              translation: 'ivory',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorMagenta,
+              translation: 'magenta',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorPeach,
+              translation: 'peach',
+            },
+            {
+              value: SeaPenTemplateOption.kTranslucentColorBlack,
+              translation: 'black',
+            },
+          ],
+        ],
+      ]),
+    },
   ];
+  // End of generated code.
   if (isSeaPenTextInputEnabled()) {
     templates.push({
       preview: [{
diff --git a/ash/webui/shimless_rma/resources/BUILD.gn b/ash/webui/shimless_rma/resources/BUILD.gn
index 0b99c75..30e4a20 100644
--- a/ash/webui/shimless_rma/resources/BUILD.gn
+++ b/ash/webui/shimless_rma/resources/BUILD.gn
@@ -48,7 +48,7 @@
     "onboarding_select_components_page.ts",
     "onboarding_update_page.ts",
     "onboarding_wait_for_manual_wp_disable_page.ts",
-    "onboarding_wp_disable_complete_page.js",
+    "onboarding_wp_disable_complete_page.ts",
     "reboot_page.js",
     "reimaging_calibration_failed_page.js",
     "reimaging_calibration_run_page.js",
diff --git a/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.js b/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.ts
similarity index 66%
rename from ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.js
rename to ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.ts
index 982abd8..fcbaee1 100644
--- a/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_wp_disable_complete_page.ts
@@ -5,16 +5,19 @@
 import './shimless_rma_shared.css.js';
 import './base_page.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 
 import {getShimlessRmaService} from './mojo_interface_provider.js';
 import {getTemplate} from './onboarding_wp_disable_complete_page.html.js';
 import {ShimlessRmaServiceInterface, StateResult, WriteProtectDisableCompleteAction} from './shimless_rma.mojom-webui.js';
 import {enableNextButton, focusPageTitle} from './shimless_rma_util.js';
 
-/** @type {!Object<WriteProtectDisableCompleteAction, string>} */
-const disableActionTextKeys = {
+type DisableActionTextKeys = {
+  [key in WriteProtectDisableCompleteAction]: string;
+};
+
+const disableActionTextKeys: DisableActionTextKeys = {
   [WriteProtectDisableCompleteAction.kSkippedAssembleDevice]:
       'wpDisableReassembleNowText',
   [WriteProtectDisableCompleteAction.kCompleteAssembleDevice]:
@@ -29,19 +32,12 @@
  * disable was successful, and what steps must be taken next.
  */
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const OnboardingWpDisableCompletePageBase =
-    mixinBehaviors([I18nBehavior], PolymerElement);
+const OnboardingWpDisableCompletePageBase = I18nMixin(PolymerElement);
 
-/** @polymer */
 export class OnboardingWpDisableCompletePage extends
     OnboardingWpDisableCompletePageBase {
   static get is() {
-    return 'onboarding-wp-disable-complete-page';
+    return 'onboarding-wp-disable-complete-page' as const;
   }
 
   static get template() {
@@ -50,7 +46,6 @@
 
   static get properties() {
     return {
-      /** @protected */
       actionString: {
         type: String,
         computed: 'getActionString(action)',
@@ -58,8 +53,11 @@
     };
   }
 
-  /** @override */
-  ready() {
+  shimlessRmaService: ShimlessRmaServiceInterface = getShimlessRmaService();
+  protected actionString: string;
+  private action: WriteProtectDisableCompleteAction = WriteProtectDisableCompleteAction.kUnknown;
+
+  override ready() {
     super.ready();
     enableNextButton(this);
 
@@ -68,40 +66,27 @@
 
   constructor() {
     super();
-    /** @private {ShimlessRmaServiceInterface} */
-    this.shimlessRmaService = getShimlessRmaService();
-    /** @private {WriteProtectDisableCompleteAction} */
-    this.action = WriteProtectDisableCompleteAction.kUnknown;
 
     this.shimlessRmaService.getWriteProtectDisableCompleteAction().then(
-        (res) => {
+        (res: {action: WriteProtectDisableCompleteAction }) => {
           if (res) {
             this.action = res.action;
           }
         });
   }
 
-  /**
-   * @return {string}
-   * @protected
-   */
-  getActionString() {
+  protected getActionString(): string {
     return (this.action === WriteProtectDisableCompleteAction.kUnknown ||
             this.action === WriteProtectDisableCompleteAction.kCompleteNoOp) ?
         '' :
         this.i18n(disableActionTextKeys[this.action]);
   }
 
-  /** @return {!Promise<!{stateResult: !StateResult}>} */
-  onNextButtonClick() {
+  onNextButtonClick(): Promise<{stateResult: StateResult}> {
     return this.shimlessRmaService.confirmManualWpDisableComplete();
   }
 
-  /**
-   * @return {string}
-   * @protected
-   */
-  getVerificationIcon() {
+  protected getVerificationIcon(): string {
     return (this.action === WriteProtectDisableCompleteAction.kUnknown ||
             this.action === WriteProtectDisableCompleteAction.kCompleteNoOp) ?
         '' :
@@ -109,5 +94,11 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    [OnboardingWpDisableCompletePage.is]: OnboardingWpDisableCompletePage;
+  }
+}
+
 customElements.define(
     OnboardingWpDisableCompletePage.is, OnboardingWpDisableCompletePage);
diff --git a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.ts b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.ts
index 4feed4d..aac0b2c 100644
--- a/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.ts
+++ b/ash/webui/shortcut_customization_ui/resources/js/shortcut_customization_app.ts
@@ -18,6 +18,7 @@
 import {NavigationViewPanelElement} from 'chrome://resources/ash/common/navigation_view_panel.js';
 import {strictQuery} from 'chrome://resources/ash/common/typescript_utils/strict_query.js';
 import {ColorChangeUpdater} from 'chrome://resources/cr_components/color_change_listener/colors_css_updater.js';
+import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import {CrToolbarSearchFieldElement} from 'chrome://resources/cr_elements/cr_toolbar/cr_toolbar_search_field.js';
 import {FindShortcutMixin} from 'chrome://resources/cr_elements/find_shortcut_mixin.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
@@ -319,7 +320,7 @@
   }
 
   protected onCancelRestoreButtonClicked(): void {
-    this.closeRestoreAllDialog();
+    strictQuery('#restoreDialog', this.shadowRoot, CrDialogElement).close();
   }
 
   protected onConfirmRestoreButtonClicked(): void {
@@ -327,7 +328,7 @@
       // TODO(jimmyxgong): Explore error state with restore all.
       if (result.result === AcceleratorConfigResult.kSuccess) {
         this.shortcutProvider.recordUserAction(UserAction.kResetAll);
-        this.closeRestoreAllDialog();
+        strictQuery('#restoreDialog', this.shadowRoot, CrDialogElement).close();
       }
     });
   }
diff --git a/ash/wm/desks/desk_button_base.cc b/ash/wm/desks/desk_button_base.cc
index 75a4b21..57cf2d8d 100644
--- a/ash/wm/desks/desk_button_base.cc
+++ b/ash/wm/desks/desk_button_base.cc
@@ -5,8 +5,8 @@
 #include "ash/wm/desks/desk_button_base.h"
 
 #include "ash/wm/desks/desk_bar_view_base.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/wm_constants.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
 #include "ui/views/border.h"
@@ -49,7 +49,7 @@
   SetBorder(views::CreateEmptyBorder(gfx::Insets()));
 
   views::InstallRoundRectHighlightPathGenerator(
-      this, gfx::Insets(kFocusRingHaloInset), kFocusRingRadius);
+      this, gfx::Insets(kWindowMiniViewFocusRingHaloInset), kFocusRingRadius);
   views::FocusRing* focus_ring = views::FocusRing::Get(this);
   focus_ring->SetOutsetFocusRingDisabled(true);
   focus_ring->SetColorId(ui::kColorAshFocusRing);
diff --git a/ash/wm/desks/desk_icon_button.cc b/ash/wm/desks/desk_icon_button.cc
index c959d2b..f9f5c87 100644
--- a/ash/wm/desks/desk_icon_button.cc
+++ b/ash/wm/desks/desk_icon_button.cc
@@ -11,7 +11,7 @@
 #include "ash/wm/desks/desk_mini_view.h"
 #include "ash/wm/desks/desk_preview_view.h"
 #include "ash/wm/desks/desks_controller.h"
-#include "ash/wm/overview/overview_constants.h"
+#include "ash/wm/wm_constants.h"
 #include "base/check_op.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
@@ -68,7 +68,7 @@
       background_color_id_(background_color_id) {
   SetEnabled(initially_enabled);
   views::InstallRoundRectHighlightPathGenerator(
-      this, gfx::Insets(kFocusRingHaloInset),
+      this, gfx::Insets(kWindowMiniViewFocusRingHaloInset),
       GetFocusRingRadiusForState(state_));
   if (bar_view_->type() == DeskBarViewBase::Type::kOverview) {
     auto* focus_ring = views::FocusRing::Get(this);
@@ -114,7 +114,7 @@
   SetBackground(views::CreateRoundedRectBackground(
       background()->get_color(), GetCornerRadiusOnState(state_)));
   views::InstallRoundRectHighlightPathGenerator(
-      this, gfx::Insets(kFocusRingHaloInset),
+      this, gfx::Insets(kWindowMiniViewFocusRingHaloInset),
       GetFocusRingRadiusForState(state_));
 }
 
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index c8b1ae2..9d64743 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -28,9 +28,9 @@
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_restore_util.h"
 #include "ash/wm/float/float_controller.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/wm_constants.h"
 #include "base/functional/bind.h"
 #include "base/i18n/rtl.h"
 #include "base/metrics/histogram_functions.h"
@@ -142,7 +142,8 @@
   views::FocusRing* preview_focus_ring = views::FocusRing::Get(desk_preview_);
   preview_focus_ring->SetOutsetFocusRingDisabled(true);
   views::InstallRoundRectHighlightPathGenerator(
-      desk_preview_, gfx::Insets(kFocusRingHaloInset), kPreviewFocusRingRadius);
+      desk_preview_, gfx::Insets(kWindowMiniViewFocusRingHaloInset),
+      kPreviewFocusRingRadius);
 
   preview_focus_ring->SetHasFocusPredicate(base::BindRepeating(
       [](const DeskMiniView* mini_view, const views::View* view) {
@@ -186,6 +187,19 @@
       },
       base::Unretained(this)));
 
+  // Only show profile avatar button when there is more than one profile logged
+  // in.
+  auto* desk_profile_delegate = Shell::Get()->GetDeskProfilesDelegate();
+  if (chromeos::features::IsDeskProfilesEnabled() &&
+      (g_force_show_desk_profiles_button ||
+       (desk_profile_delegate &&
+        desk_profile_delegate->GetProfilesSnapshot().size() > 1))) {
+    desk_profile_button_ = AddChildView(std::make_unique<DeskProfilesButton>(
+        base::BindRepeating(&DeskMiniView::OnDeskProfilesButtonPressed,
+                            base::Unretained(this)),
+        desk));
+  }
+
   desk_action_view_ = AddChildView(std::make_unique<DeskActionView>(
       desks_controller->GetCombineDesksTargetName(desk_),
       /*combine_desks_callback=*/
@@ -240,18 +254,6 @@
     desk_shortcut_view_->SetVisible(false);
     desk_shortcut_view_->SetCanProcessEventsWithinSubtree(false);
   }
-  // Only show profile avatar button when there is more than one profile logged
-  // in.
-  auto* desk_profile_delegate = Shell::Get()->GetDeskProfilesDelegate();
-  if (chromeos::features::IsDeskProfilesEnabled() &&
-      (g_force_show_desk_profiles_button ||
-       (desk_profile_delegate &&
-        desk_profile_delegate->GetProfilesSnapshot().size() > 1))) {
-    desk_profile_button_ = AddChildView(std::make_unique<DeskProfilesButton>(
-        base::BindRepeating(&DeskMiniView::OnDeskProfilesButtonPressed,
-                            base::Unretained(this)),
-        desk));
-  }
 
   UpdateDeskButtonVisibility();
 }
@@ -283,6 +285,8 @@
 
   auto* controller = DesksController::Get();
 
+  bool desk_profile_button_is_focused =
+      desk_profile_button_ && desk_profile_button_->HasFocus();
   // Don't show desk buttons when hovered while the dragged window is on
   // the desk bar view.
   // For switch access, setting desk buttons to visible allows users to
@@ -293,7 +297,8 @@
       (IsMouseHovered() || force_show_desk_buttons_ ||
        Shell::Get()->accessibility_controller()->IsSwitchAccessRunning() ||
        (owner_bar_->type() == DeskBarViewBase::Type::kDeskButton &&
-        (desk_preview_->HasFocus() || desk_action_view_->ChildHasFocus())));
+        (desk_preview_->HasFocus() || desk_profile_button_is_focused ||
+         desk_action_view_->ChildHasFocus())));
 
   // Only show the combine desks button if there are app windows in the desk,
   // or if the desk is active and there are windows that should be visible on
diff --git a/ash/wm/desks/desk_profiles_view.cc b/ash/wm/desks/desk_profiles_view.cc
index a9aa9f87..0139003 100644
--- a/ash/wm/desks/desk_profiles_view.cc
+++ b/ash/wm/desks/desk_profiles_view.cc
@@ -26,6 +26,7 @@
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/context_menu_controller.h"
 #include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/menu/menu_item_view.h"
 #include "ui/views/controls/menu/menu_model_adapter.h"
 #include "ui/views/layout/box_layout_view.h"
@@ -233,11 +234,20 @@
     : desk_(desk) {
   desk_->AddObserver(this);
   SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
+  SetPreferredSize(kIconButtonSize);
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
   icon_ = AddChildView(std::make_unique<views::ImageView>());
   icon_->SetSize(kIconButtonSize);
   icon_->SetImageSize(kIconButtonSize);
+  auto* focus_ring = views::FocusRing::Get(this);
+  focus_ring->SetOutsetFocusRingDisabled(true);
+  focus_ring->SetColorId(cros_tokens::kCrosSysFocusRing);
+  focus_ring->SetPathGenerator(
+      std::make_unique<views::CircleHighlightPathGenerator>(
+          -gfx::Insets(focus_ring->GetHaloThickness() / 2)));
+  views::InstallCircleHighlightPathGenerator(this);
+
   UpdateIcon();
   icon_->SetPaintToLayer();
   icon_->layer()->SetFillsBoundsOpaquely(false);
@@ -269,6 +279,7 @@
           desk_->lacros_profile_id())) {
     icon_image_ = summary->icon;
     icon_->SetImage(icon_image_);
+    icon_->SetTooltipText(base::UTF8ToUTF16(summary->name));
   }
 }
 
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index d9c3f53..03677456 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -24,12 +24,12 @@
 #include "ash/wm/desks/templates/saved_desk_name_view.h"
 #include "ash/wm/desks/templates/saved_desk_presenter.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_focus_cycler.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_session.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/wm_constants.h"
 #include "base/i18n/time_formatting.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
@@ -279,7 +279,7 @@
                                                 kSaveDeskCornerRadius);
 
   views::FocusRing* focus_ring =
-      StyleUtil::SetUpFocusRingForView(this, kFocusRingHaloInset);
+      StyleUtil::SetUpFocusRingForView(this, kWindowMiniViewFocusRingHaloInset);
   focus_ring->SetHasFocusPredicate(
       base::BindRepeating([](const views::View* view) {
         const auto* v = views::AsViewClass<SavedDeskItemView>(view);
diff --git a/ash/wm/overview/overview_constants.h b/ash/wm/overview/overview_constants.h
index 40d3e4f..897c240 100644
--- a/ash/wm/overview/overview_constants.h
+++ b/ash/wm/overview/overview_constants.h
@@ -24,24 +24,11 @@
 // The amount we want to enlarge the dragged overview window.
 constexpr int kDraggingEnlargeDp = 10;
 
-// Height of an item header.
-constexpr int kHeaderHeightDp = WindowMiniView::kHeaderHeightDp;
-
-// Corner radius of the overview item.
-constexpr int kOverviewItemCornerRadius =
-    WindowMiniView::kWindowMiniViewCornerRadius;
-
 // Windows whose aspect ratio surpass this (width twice as large as height
 // or vice versa) will be classified as too wide or too tall and will be
 // handled slightly differently in overview mode.
 constexpr float kExtremeWindowRatioThreshold = 2.f;
 
-// Inset for the focus ring around the focusable overview items. The ring is 2px
-// thick and should have a 2px gap from the view it is associated with. Since
-// the thickness is 2px and the stroke is in the middle, we use a -3px inset to
-// achieve this.
-constexpr int kFocusRingHaloInset = -3;
-
 // The shadow types corresponding to the default and dragged states.
 constexpr SystemShadow::Type kDefaultShadowType =
     SystemShadow::Type::kElevation12;
diff --git a/ash/wm/overview/overview_drop_target.cc b/ash/wm/overview/overview_drop_target.cc
index d187e4e..a94502a 100644
--- a/ash/wm/overview/overview_drop_target.cc
+++ b/ash/wm/overview/overview_drop_target.cc
@@ -7,9 +7,9 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/wm/desks/desks_util.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_utils.h"
+#include "ash/wm/wm_constants.h"
 #include "base/memory/raw_ptr.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -36,11 +36,11 @@
 
     background_view_ = AddChildView(std::make_unique<views::View>());
     background_view_->SetBackground(views::CreateThemedRoundedRectBackground(
-        kColorAshShieldAndBase20, kOverviewItemCornerRadius,
+        kColorAshShieldAndBase20, kWindowMiniViewCornerRadius,
         /*for_border_thickness=*/0));
 
     SetBorder(views::CreateThemedRoundedRectBorder(
-        kDropTargetBorderThickness, kOverviewItemCornerRadius,
+        kDropTargetBorderThickness, kWindowMiniViewCornerRadius,
         cros_tokens::kCrosSysSystemBaseElevated));
   }
   OverviewDropTargetView(const OverviewDropTargetView&) = delete;
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 4fe46d46..b081426 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -63,6 +63,7 @@
 #include "ash/wm/window_restore/pine_contents_view.h"
 #include "ash/wm/window_state_delegate.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "ash/wm/workspace/backdrop_controller.h"
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
@@ -1746,7 +1747,7 @@
 
       scale = ScopedOverviewTransformWindow::GetItemScale(
           target_size.height(), height, GetTopViewInset(dragged_windows),
-          kHeaderHeightDp);
+          kWindowMiniViewHeaderHeight);
     }
   }
 
@@ -1771,7 +1772,8 @@
   // is nothing from the original window to be shown and nothing to be clipped.
   std::optional<gfx::RectF> split_view_bounds =
       GetSplitviewBoundsMaintainingAspectRatio();
-  if (!split_view_bounds || split_view_bounds->height() < kHeaderHeightDp) {
+  if (!split_view_bounds ||
+      split_view_bounds->height() < kWindowMiniViewHeaderHeight) {
     item->set_unclipped_size(std::nullopt);
     return width;
   }
@@ -1785,7 +1787,7 @@
   const float target_aspect_ratio =
       split_view_bounds->width() / split_view_bounds->height();
   const bool clip_horizontally = aspect_ratio > target_aspect_ratio;
-  const int window_height = height - kHeaderHeightDp;
+  const int window_height = height - kWindowMiniViewHeaderHeight;
   gfx::Size unclipped_size;
   if (clip_horizontally) {
     unclipped_size.set_width(width);
@@ -1807,7 +1809,7 @@
     const int unclipped_height =
         width * target_size.height() / target_size.width();
     unclipped_size.set_width(width);
-    unclipped_size.set_height(unclipped_height + kHeaderHeightDp);
+    unclipped_size.set_height(unclipped_height + kWindowMiniViewHeaderHeight);
   }
 
   DCHECK(!unclipped_size.IsEmpty());
diff --git a/ash/wm/overview/overview_group_item.cc b/ash/wm/overview/overview_group_item.cc
index 335e25b..14cc02c 100644
--- a/ash/wm/overview/overview_group_item.cc
+++ b/ash/wm/overview/overview_group_item.cc
@@ -16,6 +16,7 @@
 #include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "base/check_op.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/functional/callback_helpers.h"
@@ -220,7 +221,8 @@
 
 gfx::RectF OverviewGroupItem::GetTargetBoundsWithInsets() const {
   gfx::RectF target_bounds_with_insets = target_bounds_;
-  target_bounds_with_insets.Inset(gfx::InsetsF::TLBR(kHeaderHeightDp, 0, 0, 0));
+  target_bounds_with_insets.Inset(
+      gfx::InsetsF::TLBR(kWindowMiniViewHeaderHeight, 0, 0, 0));
   return target_bounds_with_insets;
 }
 
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 46a1b49a..e05fb94 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -38,6 +38,7 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "base/auto_reset.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -376,7 +377,7 @@
   if (unclipped_size_) {
     gfx::SizeF target_size(*unclipped_size_);
     gfx::SizeF preview_size = GetTargetBoundsWithInsets().size();
-    target_size.Enlarge(0, -kHeaderHeightDp);
+    target_size.Enlarge(0, -kWindowMiniViewHeaderHeight);
 
     const float x_scale = target_size.width() / preview_size.width();
     const float y_scale = target_size.height() / preview_size.height();
@@ -443,7 +444,8 @@
   const int top_view_inset = transform_window_.GetTopInset();
   gfx::RectF overview_item_bounds =
       transform_window_.ShrinkRectToFitPreservingAspectRatio(
-          screen_rect, transformed_bounds, top_view_inset, kHeaderHeightDp);
+          screen_rect, transformed_bounds, top_view_inset,
+          kWindowMiniViewHeaderHeight);
 
   if (transform_window_.type() == OverviewGridWindowFillMode::kNormal ||
       transform_window_.type() == OverviewGridWindowFillMode::kLetterBoxed) {
@@ -464,7 +466,8 @@
       overview_item_bounds.set_y(
           overview_item_view_->header_view()->GetBoundsInScreen().bottom() -
           window_top_inset_target_height);
-      overview_item_bounds.set_height(target_bounds.height() - kHeaderHeightDp +
+      overview_item_bounds.set_height(target_bounds.height() -
+                                      kWindowMiniViewHeaderHeight +
                                       window_top_inset_target_height);
     }
   }
@@ -517,7 +520,7 @@
 
 gfx::RectF OverviewItem::GetTargetBoundsWithInsets() const {
   gfx::RectF target_bounds = target_bounds_;
-  target_bounds.Inset(gfx::InsetsF::TLBR(kHeaderHeightDp, 0, 0, 0));
+  target_bounds.Inset(gfx::InsetsF::TLBR(kWindowMiniViewHeaderHeight, 0, 0, 0));
   return target_bounds;
 }
 
@@ -528,7 +531,7 @@
 float OverviewItem::GetItemScale(int height) {
   return ScopedOverviewTransformWindow::GetItemScale(
       GetWindowsUnionScreenBounds().height(), height,
-      transform_window_.GetTopInset(), kHeaderHeightDp);
+      transform_window_.GetTopInset(), kWindowMiniViewHeaderHeight);
 }
 
 void OverviewItem::ScaleUpSelectedItem(OverviewAnimationType animation_type) {
diff --git a/ash/wm/overview/overview_item_base.cc b/ash/wm/overview/overview_item_base.cc
index d65e864..d349510 100644
--- a/ash/wm/overview/overview_item_base.cc
+++ b/ash/wm/overview/overview_item_base.cc
@@ -21,6 +21,7 @@
 #include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/splitview/split_view_utils.h"
+#include "ash/wm/wm_constants.h"
 #include "base/memory/raw_ptr.h"
 
 namespace ash {
@@ -81,7 +82,7 @@
   gfx::Rect shadow_content_bounds(
       gfx::ToRoundedRect(shadow_bounds_in_screen).size());
   shadow_->SetContentBounds(shadow_content_bounds);
-  shadow_->SetRoundedCornerRadius(kOverviewItemCornerRadius);
+  shadow_->SetRoundedCornerRadius(kWindowMiniViewCornerRadius);
 }
 
 void OverviewItemBase::UpdateShadowTypeForDrag(bool is_dragging) {
diff --git a/ash/wm/overview/overview_item_view.cc b/ash/wm/overview/overview_item_view.cc
index 5a27419..09ddafd7c 100644
--- a/ash/wm/overview/overview_item_view.cc
+++ b/ash/wm/overview/overview_item_view.cc
@@ -8,19 +8,20 @@
 
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/close_button.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_item.h"
 #include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/window_mini_view_header_view.h"
 #include "ash/wm/window_preview_view.h"
+#include "ash/wm/wm_constants.h"
 #include "base/containers/contains.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/aura/window.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/size_conversions.h"
 #include "ui/strings/grit/ui_strings.h"
 #include "ui/views/animation/animation_builder.h"
@@ -184,13 +185,13 @@
       SetRoundedCornersRadius(
           window == snap_group->window1()
               ? gfx::RoundedCornersF(
-                    /*upper_left=*/kOverviewItemCornerRadius,
+                    /*upper_left=*/kWindowMiniViewCornerRadius,
                     /*upper_right=*/0, /*lower_right=*/0,
-                    /*lower_left=*/kOverviewItemCornerRadius)
+                    /*lower_left=*/kWindowMiniViewCornerRadius)
               : gfx::RoundedCornersF(
                     /*upper_left=*/0,
-                    /*upper_right=*/kOverviewItemCornerRadius,
-                    /*lower_right=*/kOverviewItemCornerRadius,
+                    /*upper_right=*/kWindowMiniViewCornerRadius,
+                    /*lower_right=*/kWindowMiniViewCornerRadius,
                     /*lower_left=*/0));
     }
   }
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index a7fbb4d..be8d5191 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -80,6 +80,7 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_state_delegate.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/workspace/workspace_window_resizer.h"
 #include "base/containers/contains.h"
@@ -864,7 +865,8 @@
   std::unique_ptr<views::Widget> widget(CreateTestWidget());
   widget->SetBounds(gfx::Rect(650, 0, 400, 400));
   aura::Window* window2 = widget->GetNativeWindow();
-  window2->SetProperty(aura::client::kTopViewInset, kHeaderHeightDp);
+  window2->SetProperty(aura::client::kTopViewInset,
+                       kWindowMiniViewHeaderHeight);
   views::Widget::ReparentNativeView(window2, window->parent());
   ASSERT_EQ(Shell::GetAllRootWindows()[1], window2->GetRootWindow());
 
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index 0463ffa4..687fdc2d 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -30,6 +30,7 @@
 #include "ash/wm/splitview/split_view_utils.h"
 #include "ash/wm/window_positioning_utils.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "base/debug/crash_logging.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -150,7 +151,8 @@
   }
 
   // Add the margins overview mode adds around the window's contents.
-  scaled_size.Enlarge(kDraggingEnlargeDp, kDraggingEnlargeDp + kHeaderHeightDp);
+  scaled_size.Enlarge(kDraggingEnlargeDp,
+                      kDraggingEnlargeDp + kWindowMiniViewHeaderHeight);
   return scaled_size;
 }
 
@@ -398,8 +400,9 @@
     // bottom-edge of the desks bar (may be different edges if we are dragging
     // from different directions).
     gfx::SizeF item_no_header_size = original_scaled_size_;
-    item_no_header_size.Enlarge(float{-kDraggingEnlargeDp},
-                                float{-kDraggingEnlargeDp - kHeaderHeightDp});
+    item_no_header_size.Enlarge(
+        float{-kDraggingEnlargeDp},
+        float{-kDraggingEnlargeDp - kWindowMiniViewHeaderHeight});
 
     // We must update the desks bar widget bounds before we cache its bounds
     // below, in case it needs to be pushed down due to splitview indicators.
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index f6d962b..bf333bf 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include "ash/wm/overview/scoped_overview_transform_window.h"
-#include "base/memory/raw_ptr.h"
 
 #include <algorithm>
 #include <utility>
@@ -29,7 +28,9 @@
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_transient_descendant_iterator.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
@@ -110,12 +111,12 @@
                        /*upper_left=*/0,
                        /*upper_right=*/0, /*lower_right=*/0,
                        /*lower_left=*/
-                       kOverviewItemCornerRadius / scale)
+                       kWindowMiniViewCornerRadius / scale)
                  : gfx::RoundedCornersF(
                        /*upper_left=*/0,
                        /*upper_right=*/0,
                        /*lower_right=*/
-                       kOverviewItemCornerRadius / scale,
+                       kWindowMiniViewCornerRadius / scale,
                        /*lower_left=*/0);
     }
   }
@@ -123,8 +124,8 @@
   return gfx::RoundedCornersF(
       /*upper_left=*/0,
       /*upper_right=*/0,
-      /*lower_right=*/kOverviewItemCornerRadius / scale,
-      /*lower_left=*/kOverviewItemCornerRadius / scale);
+      /*lower_right=*/kWindowMiniViewCornerRadius / scale,
+      /*lower_left=*/kWindowMiniViewCornerRadius / scale);
 }
 
 }  // namespace
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index 0598e68..a401397 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/wm/snap_group/snap_group.h"
+
 #include <memory>
 #include <vector>
 
@@ -34,7 +36,6 @@
 #include "ash/wm/overview/overview_utils.h"
 #include "ash/wm/overview/overview_window_drag_controller.h"
 #include "ash/wm/overview/scoped_overview_transform_window.h"
-#include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
@@ -51,6 +52,7 @@
 #include "ash/wm/window_resizer.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "ash/wm/wm_event.h"
 #include "ash/wm/wm_metrics.h"
 #include "ash/wm/workspace/multi_window_resize_controller.h"
@@ -87,8 +89,6 @@
 
 using WindowCyclingDirection = WindowCycleController::WindowCyclingDirection;
 
-constexpr int kWindowMiniViewCornerRadius = 16;
-
 SplitViewController* split_view_controller() {
   return SplitViewController::Get(Shell::GetPrimaryRootWindow());
 }
diff --git a/ash/wm/window_cycle/window_cycle_item_view.cc b/ash/wm/window_cycle/window_cycle_item_view.cc
index fdae31db..7593497 100644
--- a/ash/wm/window_cycle/window_cycle_item_view.cc
+++ b/ash/wm/window_cycle/window_cycle_item_view.cc
@@ -15,6 +15,7 @@
 #include "ash/wm/window_mini_view_header_view.h"
 #include "ash/wm/window_preview_view.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "ui/accessibility/ax_action_data.h"
 #include "ui/aura/window.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -124,7 +125,7 @@
       std::clamp(preview_size.width(), kMinPreviewWidthDp, kMaxPreviewWidthDp));
 
   const int margin = GetInsets().width();
-  preview_size.Enlarge(margin, margin + WindowMiniView::kHeaderHeightDp);
+  preview_size.Enlarge(margin, margin + kWindowMiniViewHeaderHeight);
   return preview_size;
 }
 
@@ -202,13 +203,13 @@
 void GroupContainerCycleView::RefreshItemVisuals() {
   if (mini_views_.size() == 2u) {
     mini_views_[0]->SetRoundedCornersRadius(gfx::RoundedCornersF(
-        /*upper_left=*/WindowMiniView::kWindowMiniViewCornerRadius,
+        /*upper_left=*/kWindowMiniViewCornerRadius,
         /*upper_right=*/0, /*lower_right=*/0,
-        /*lower_left=*/WindowMiniView::kWindowMiniViewCornerRadius));
+        /*lower_left=*/kWindowMiniViewCornerRadius));
     mini_views_[1]->SetRoundedCornersRadius(gfx::RoundedCornersF(
         /*upper_left=*/0,
-        /*upper_right=*/WindowMiniView::kWindowMiniViewCornerRadius,
-        /*lower_right=*/WindowMiniView::kWindowMiniViewCornerRadius,
+        /*upper_right=*/kWindowMiniViewCornerRadius,
+        /*lower_right=*/kWindowMiniViewCornerRadius,
         /*lower_left=*/0));
   }
 
diff --git a/ash/wm/window_cycle/window_cycle_view.cc b/ash/wm/window_cycle/window_cycle_view.cc
index ea9cf05..5ef138e 100644
--- a/ash/wm/window_cycle/window_cycle_view.cc
+++ b/ash/wm/window_cycle/window_cycle_view.cc
@@ -24,6 +24,7 @@
 #include "ash/wm/window_cycle/window_cycle_item_view.h"
 #include "ash/wm/window_cycle/window_cycle_list.h"
 #include "ash/wm/window_mini_view.h"
+#include "ash/wm/wm_constants.h"
 #include "base/check_op.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
@@ -240,13 +241,12 @@
                 no_recent_items_label_->font_list().GetFontSize())
             .DeriveWithWeight(gfx::Font::Weight::NORMAL));
     no_recent_items_label_->SetVisible(windows.empty());
-    no_recent_items_label_->SetPreferredSize(
-        gfx::Size(tab_slider_->GetPreferredSize().width() +
-                      2 * WindowCycleView::kInsideBorderHorizontalPaddingDp,
-                  WindowCycleItemView::kFixedPreviewHeightDp +
-                      WindowMiniView::kHeaderHeightDp +
-                      kMirrorContainerVerticalPaddingDp +
-                      kInsideBorderVerticalPaddingDp + 8));
+    no_recent_items_label_->SetPreferredSize(gfx::Size(
+        tab_slider_->GetPreferredSize().width() +
+            2 * WindowCycleView::kInsideBorderHorizontalPaddingDp,
+        WindowCycleItemView::kFixedPreviewHeightDp +
+            kWindowMiniViewHeaderHeight + kMirrorContainerVerticalPaddingDp +
+            kInsideBorderVerticalPaddingDp + 8));
   }
 
   for (aura::Window* window : windows) {
diff --git a/ash/wm/window_mini_view.cc b/ash/wm/window_mini_view.cc
index bb69758..3f28c538 100644
--- a/ash/wm/window_mini_view.cc
+++ b/ash/wm/window_mini_view.cc
@@ -7,12 +7,12 @@
 #include <memory>
 
 #include "ash/shell.h"
-#include "ash/wm/overview/overview_constants.h"
 #include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/window_mini_view_header_view.h"
 #include "ash/wm/window_preview_view.h"
 #include "ash/wm/window_util.h"
+#include "ash/wm/wm_constants.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -63,9 +63,8 @@
                                 raw_value.lower_left());
   }
 
-  return gfx::RoundedCornersF(
-      0, 0, WindowMiniView::kWindowMiniViewCornerRadius / scale,
-      WindowMiniView::kWindowMiniViewCornerRadius / scale);
+  return gfx::RoundedCornersF(0, 0, kWindowMiniViewCornerRadius / scale,
+                              kWindowMiniViewCornerRadius / scale);
 }
 
 }  // namespace
@@ -156,9 +155,18 @@
     return;
   }
 
+  const auto header_rounded_corners =
+      header_view_->GetHeaderRoundedCorners(source_window_);
   if (header_view_rounded_corners_.has_value()) {
+    if (header_rounded_corners == header_view_rounded_corners_.value()) {
+      return;
+    }
     header_view_->SetHeaderViewRoundedCornerRadius(
         header_view_rounded_corners_.value());
+  } else if (header_rounded_corners ==
+             gfx::RoundedCornersF(kWindowMiniViewHeaderCornerRadius,
+                                  kWindowMiniViewHeaderCornerRadius, 0, 0)) {
+    return;
   }
 
   header_view_->RefreshHeaderViewRoundedCorners();
@@ -229,7 +237,7 @@
 
 gfx::Rect WindowMiniView::GetHeaderBounds() const {
   gfx::Rect header_bounds = GetContentsBounds();
-  header_bounds.set_height(kHeaderHeightDp);
+  header_bounds.set_height(kWindowMiniViewHeaderHeight);
   return header_bounds;
 }
 
@@ -247,7 +255,7 @@
 
 gfx::Rect WindowMiniView::GetContentAreaBounds() const {
   gfx::Rect bounds(GetContentsBounds());
-  bounds.Inset(gfx::Insets::TLBR(kHeaderHeightDp, 0, 0, 0));
+  bounds.Inset(gfx::Insets::TLBR(kWindowMiniViewHeaderHeight, 0, 0, 0));
   return bounds;
 }
 
@@ -336,12 +344,12 @@
                                  ? 0
                                  : kFocusRingCornerRadius;
     return std::make_unique<views::RoundRectHighlightPathGenerator>(
-        gfx::Insets(kFocusRingHaloInset),
+        gfx::Insets(kWindowMiniViewFocusRingHaloInset),
         gfx::RoundedCornersF(upper_left, upper_right, lower_right, lower_left));
   }
 
   return std::make_unique<views::RoundRectHighlightPathGenerator>(
-      gfx::Insets(kFocusRingHaloInset),
+      gfx::Insets(kWindowMiniViewFocusRingHaloInset),
       gfx::RoundedCornersF(kFocusRingCornerRadius));
 }
 
diff --git a/ash/wm/window_mini_view.h b/ash/wm/window_mini_view.h
index ecc392b..3cd11ce 100644
--- a/ash/wm/window_mini_view.h
+++ b/ash/wm/window_mini_view.h
@@ -107,19 +107,10 @@
   WindowMiniView& operator=(const WindowMiniView&) = delete;
   ~WindowMiniView() override;
 
-  static constexpr int kHeaderHeightDp = 40;
   // The size in dp of the window icon shown on the alt-tab/overview window next
   // to the title.
   static constexpr gfx::Size kIconSize = gfx::Size(24, 24);
 
-  // The corner radius for WindowMiniView. Note that instead of setting the
-  // corner radius directly on the window mini view, setting the corner radius
-  // on its children (header view, preview header). The reasons are:
-  // 1. The WindowMiniView might have a non-empty border.
-  // 2. The focus ring which is a child view of the WindowMiniView couldn't be
-  // drawn correctly if its parent's layer is clipped.
-  static constexpr int kWindowMiniViewCornerRadius = 16;
-
   aura::Window* source_window() { return source_window_; }
   const aura::Window* source_window() const { return source_window_; }
   WindowMiniViewHeaderView* header_view() { return header_view_; }
diff --git a/ash/wm/window_mini_view_header_view.cc b/ash/wm/window_mini_view_header_view.cc
index 0b4bc9e..f84aa482 100644
--- a/ash/wm/window_mini_view_header_view.cc
+++ b/ash/wm/window_mini_view_header_view.cc
@@ -9,6 +9,7 @@
 #include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/window_mini_view.h"
+#include "ash/wm/wm_constants.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -33,9 +34,6 @@
 // Padding between header items.
 constexpr int kHeaderPaddingDp = 8;
 
-// The corner radius for the top corners for the header.
-constexpr int kHeaderTopCornerRadius = 16;
-
 // The size in dp of the window icon shown on the alt-tab/overview window next
 // to the title.
 constexpr gfx::Size kIconSize = gfx::Size(24, 24);
@@ -127,8 +125,9 @@
 
 gfx::RoundedCornersF WindowMiniViewHeaderView::GetHeaderRoundedCorners(
     aura::Window* window) const {
-  return header_view_rounded_corners_.value_or(gfx::RoundedCornersF(
-      kHeaderTopCornerRadius, kHeaderTopCornerRadius, 0, 0));
+  return header_view_rounded_corners_.value_or(
+      gfx::RoundedCornersF(kWindowMiniViewHeaderCornerRadius,
+                           kWindowMiniViewHeaderCornerRadius, 0, 0));
 }
 
 BEGIN_METADATA(WindowMiniViewHeaderView, views::View)
diff --git a/ash/wm/wm_constants.h b/ash/wm/wm_constants.h
new file mode 100644
index 0000000..53235ae
--- /dev/null
+++ b/ash/wm/wm_constants.h
@@ -0,0 +1,34 @@
+// Copyright 2024 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_WM_WM_CONSTANTS_H_
+#define ASH_WM_WM_CONSTANTS_H_
+
+#include "ash/ash_export.h"
+
+namespace ash {
+
+// The corner radius for the top corners of `WindowMiniView`.
+ASH_EXPORT constexpr int kWindowMiniViewHeaderCornerRadius = 16;
+
+// The corner radius for WindowMiniView. Note that instead of setting the
+// corner radius directly on the window mini view, setting the corner radius
+// on its children (header view, preview header). The reasons are:
+// 1. The WindowMiniView might have a non-empty border.
+// 2. The focus ring which is a child view of the WindowMiniView couldn't be
+// drawn correctly if its parent's layer is clipped.
+ASH_EXPORT constexpr int kWindowMiniViewCornerRadius = 16;
+
+// Height value for the header view.
+ASH_EXPORT constexpr int kWindowMiniViewHeaderHeight = 40;
+
+// Inset for the focus ring around the focusable `WindowMiniView` items. The
+// ring is 2px thick and should have a 2px gap from the view it is associated
+// with. Since the thickness is 2px and the stroke is in the middle, we use a
+// -3px inset to achieve this.
+constexpr int kWindowMiniViewFocusRingHaloInset = -3;
+
+}  // namespace ash
+
+#endif  // ASH_WM_WM_CONSTANTS_H_
diff --git a/base/containers/heap_array.h b/base/containers/heap_array.h
index e0bcf91..f45bef9 100644
--- a/base/containers/heap_array.h
+++ b/base/containers/heap_array.h
@@ -91,31 +91,31 @@
   T* data() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data_.get(); }
   const T* data() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return data_.get(); }
 
-  iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return span().begin(); }
+  iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return as_span().begin(); }
   const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().begin();
+    return as_span().begin();
   }
 
-  iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return span().end(); }
+  iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return as_span().end(); }
   const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().end();
+    return as_span().end();
   }
 
   T& operator[](size_t idx) ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span()[idx];
+    return as_span()[idx];
   }
   const T& operator[](size_t idx) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span()[idx];
+    return as_span()[idx];
   }
 
   // Access the HeapArray via spans. Note that span<T> is implicilty
-  // constructible from HeapArray<T>, so an explicit call to .span() is
+  // constructible from HeapArray<T>, so an explicit call to .as_span() is
   // most useful, say, when the compiler can't deduce a template
   // argument type.
-  base::span<T> span() ABSL_ATTRIBUTE_LIFETIME_BOUND {
+  base::span<T> as_span() ABSL_ATTRIBUTE_LIFETIME_BOUND {
     return base::span<T>(data_.get(), size_);
   }
-  base::span<const T> span() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
+  base::span<const T> as_span() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
     return base::span<const T>(data_.get(), size_);
   }
 
@@ -126,30 +126,30 @@
   // the HeapArray.
   base::span<T> subspan(size_t offset, size_t count = base::dynamic_extent)
       ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().subspan(offset, count);
+    return as_span().subspan(offset, count);
   }
   base::span<const T> subspan(size_t offset,
                               size_t count = base::dynamic_extent) const
       ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().subspan(offset, count);
+    return as_span().subspan(offset, count);
   }
 
   // Returns a span over the first `count` elements of the HeapArray. A CHECK()
   // occurs if the `count` is larger than size of the HeapArray.
   base::span<T> first(size_t count) ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().first(count);
+    return as_span().first(count);
   }
   base::span<const T> first(size_t count) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().first(count);
+    return as_span().first(count);
   }
 
   // Returns a span over the last `count` elements of the HeapArray. A CHECK()
   // occurs if the `count` is larger than size of the HeapArray.
   base::span<T> last(size_t count) ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().last(count);
+    return as_span().last(count);
   }
   base::span<const T> last(size_t count) const ABSL_ATTRIBUTE_LIFETIME_BOUND {
-    return span().last(count);
+    return as_span().last(count);
   }
 
  private:
diff --git a/base/containers/heap_array_nocompile.nc b/base/containers/heap_array_nocompile.nc
index dda34838..03d73ff 100644
--- a/base/containers/heap_array_nocompile.nc
+++ b/base/containers/heap_array_nocompile.nc
@@ -58,7 +58,7 @@
 }
 
 base::span<int> WontCompileSpanLifetime() {
-  return HeapArray<int>::WithSize(1u).span(); // expected-error {{returning address}}
+  return HeapArray<int>::WithSize(1u).as_span(); // expected-error {{returning address}}
 }
 
 }  // namespace
diff --git a/base/containers/heap_array_unittest.cc b/base/containers/heap_array_unittest.cc
index 94940c0..32d236c 100644
--- a/base/containers/heap_array_unittest.cc
+++ b/base/containers/heap_array_unittest.cc
@@ -110,6 +110,23 @@
   EXPECT_DEATH_IF_SUPPORTED(vec[2], "");
 }
 
+TEST(HeapArray, AsSpan) {
+  {
+    auto vec = HeapArray<uint32_t>::WithSize(2u);
+    auto s = vec.as_span();
+    static_assert(std::same_as<decltype(s), span<uint32_t>>);
+    EXPECT_EQ(s.size(), 2u);
+    EXPECT_EQ(s.data(), vec.data());
+  }
+  {
+    const auto vec = HeapArray<uint32_t>::WithSize(2u);
+    auto s = vec.as_span();
+    static_assert(std::same_as<decltype(s), span<const uint32_t>>);
+    EXPECT_EQ(s.size(), 2u);
+    EXPECT_EQ(s.data(), vec.data());
+  }
+}
+
 TEST(HeapArray, Subspan) {
   auto vec = HeapArray<uint32_t>::WithSize(4u);
   for (size_t i = 0; i < vec.size(); ++i) {
diff --git a/base/containers/span.h b/base/containers/span.h
index 6c83166..db8d969 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <algorithm>
 #include <array>
 #include <concepts>
 #include <iterator>
@@ -186,6 +187,7 @@
 // - as_chars() function.
 // - as_writable_chars() function.
 // - as_byte_span() function.
+// - copy_from() method.
 //
 // Furthermore, all constructors and methods are marked noexcept due to the lack
 // of exceptions in Chromium.
@@ -341,6 +343,24 @@
     return reverse_iterator(begin());
   }
 
+  // Bounds-checked copy of spans into spans. The spans must be the exact
+  // same size or a hard CHECK() occurs. This is a non-std extension that
+  // is inspired by the Rust slice::copy_from_slice() method.
+  template <typename U, size_t M>
+  void copy_from(const span<U, M>& other)
+    requires(M != dynamic_extent && internal::LegalDataConversion<T, U>)
+  {
+    static_assert(N == M, "span size mismatch");
+    std::ranges::copy(other, data());
+  }
+  template <typename U, size_t M>
+  void copy_from(const span<U, M>& other)
+    requires(M == dynamic_extent && internal::LegalDataConversion<T, U>)
+  {
+    CHECK_EQ(size_bytes(), other.size_bytes());
+    std::ranges::copy(other, data());
+  }
+
  private:
   // This field is not a raw_ptr<> because it was filtered by the rewriter
   // for: #constexpr-ctor-field-initializer, #global-scope, #union
@@ -490,6 +510,17 @@
     return reverse_iterator(begin());
   }
 
+  // Bounds-checked copy of spans into spans. The spans must be the exact
+  // same size or a hard CHECK() occurs. This is a non-std extension that
+  // is inspired by the Rust slice::copy_from_slice() method.
+  template <typename U, size_t M>
+  void copy_from(const span<U, M>& other)
+    requires(internal::LegalDataConversion<T, U>)
+  {
+    CHECK_EQ(size_bytes(), other.size_bytes());
+    std::ranges::copy(other, data());
+  }
+
  private:
   // This field is not a raw_ptr<> because it was filtered by the rewriter
   // for: #constexpr-ctor-field-initializer, #global-scope, #union
diff --git a/base/containers/span_nocompile.nc b/base/containers/span_nocompile.nc
index 20b0a41..7651649 100644
--- a/base/containers/span_nocompile.nc
+++ b/base/containers/span_nocompile.nc
@@ -172,4 +172,10 @@
   span<int> s(array);  // expected-error {{no matching constructor for initialization of 'span<int>'}}
 }
 
+void FixedSizeCopyTooSmall() {
+  const int src[] = {1, 2, 3};
+  int dst[2];
+  base::make_span(dst).copy_from(base::make_span(src));  // expected-error@*:* {{span size mismatch}}
+}
+
 }  // namespace base
diff --git a/base/containers/span_unittest.cc b/base/containers/span_unittest.cc
index 789d0ff..afa9e3f 100644
--- a/base/containers/span_unittest.cc
+++ b/base/containers/span_unittest.cc
@@ -1672,4 +1672,41 @@
   static_assert(EXTENT(plain_array) == kSize, "EXTENT broken for plain arrays");
 }
 
+TEST(SpanTest, CopyFrom) {
+  int arr[] = {1, 2, 3};
+  span<int, 0> empty_static_span;
+  span<int, 3> static_span = base::make_span(arr);
+
+  std::vector<int> vec = {4, 5, 6};
+  span<int> empty_dynamic_span;
+  span<int> dynamic_span = base::make_span(vec);
+
+  // Handle empty cases gracefully.
+  empty_static_span.copy_from(empty_dynamic_span);
+  empty_dynamic_span.copy_from(empty_static_span);
+  static_span.first(empty_static_span.size()).copy_from(empty_static_span);
+  dynamic_span.first(empty_dynamic_span.size()).copy_from(empty_dynamic_span);
+  EXPECT_THAT(arr, ElementsAre(1, 2, 3));
+  EXPECT_THAT(vec, ElementsAre(4, 5, 6));
+
+  // Test too small destinations.
+  EXPECT_DEATH_IF_SUPPORTED(empty_static_span.copy_from(dynamic_span), "");
+  EXPECT_DEATH_IF_SUPPORTED(empty_dynamic_span.copy_from(static_span), "");
+  EXPECT_DEATH_IF_SUPPORTED(empty_dynamic_span.copy_from(dynamic_span), "");
+  EXPECT_DEATH_IF_SUPPORTED(static_span.first(2).copy_from(dynamic_span), "");
+  EXPECT_DEATH_IF_SUPPORTED(dynamic_span.last(2).copy_from(static_span), "");
+
+  static_span.first(2).copy_from(static_span.last(2));
+  EXPECT_THAT(arr, ElementsAre(2, 3, 3));
+
+  dynamic_span.first(2).copy_from(dynamic_span.last(2));
+  EXPECT_THAT(vec, ElementsAre(5, 6, 6));
+
+  static_span.last(1).copy_from(dynamic_span.last(1));
+  EXPECT_THAT(arr, ElementsAre(2, 3, 6));
+
+  dynamic_span.first(1).copy_from(static_span.first(1));
+  EXPECT_THAT(vec, ElementsAre(2, 6, 6));
+}
+
 }  // namespace base
diff --git a/base/files/file.cc b/base/files/file.cc
index ae486b4..ce02b46 100644
--- a/base/files/file.cc
+++ b/base/files/file.cc
@@ -99,26 +99,44 @@
 }
 #endif
 
-bool File::ReadAndCheck(int64_t offset, span<uint8_t> data) {
+int File::Read(int64_t offset, span<uint8_t> data) {
   int size = checked_cast<int>(data.size());
-  return Read(offset, reinterpret_cast<char*>(data.data()), size) == size;
+  return Read(offset, reinterpret_cast<char*>(data.data()), size);
+}
+
+bool File::ReadAndCheck(int64_t offset, span<uint8_t> data) {
+  // Size checked in span form of Read() above.
+  return Read(offset, data) == static_cast<int>(data.size());
+}
+
+int File::ReadAtCurrentPos(span<uint8_t> data) {
+  int size = checked_cast<int>(data.size());
+  return ReadAtCurrentPos(reinterpret_cast<char*>(data.data()), size);
 }
 
 bool File::ReadAtCurrentPosAndCheck(span<uint8_t> data) {
+  // Size checked in span form of ReadAtCurrentPos() above.
+  return ReadAtCurrentPos(data) == static_cast<int>(data.size());
+}
+
+int File::Write(int64_t offset, span<const uint8_t> data) {
   int size = checked_cast<int>(data.size());
-  return ReadAtCurrentPos(reinterpret_cast<char*>(data.data()), size) == size;
+  return Write(offset, reinterpret_cast<const char*>(data.data()), size);
 }
 
 bool File::WriteAndCheck(int64_t offset, span<const uint8_t> data) {
+  // Size checked in span form of Write() above.
+  return Write(offset, data) == static_cast<int>(data.size());
+}
+
+int File::WriteAtCurrentPos(span<const uint8_t> data) {
   int size = checked_cast<int>(data.size());
-  return Write(offset, reinterpret_cast<const char*>(data.data()), size) ==
-         size;
+  return WriteAtCurrentPos(reinterpret_cast<const char*>(data.data()), size);
 }
 
 bool File::WriteAtCurrentPosAndCheck(span<const uint8_t> data) {
-  int size = checked_cast<int>(data.size());
-  return WriteAtCurrentPos(reinterpret_cast<const char*>(data.data()), size) ==
-         size;
+  // Size checked in span form of WriteAtCurrentPos() above.
+  return WriteAtCurrentPos(data) == static_cast<int>(data.size());
 }
 
 // static
diff --git a/base/files/file.h b/base/files/file.h
index 1e0b440..0d8eb4676 100644
--- a/base/files/file.h
+++ b/base/files/file.h
@@ -220,9 +220,11 @@
   // normal expectation is that actually |size| bytes are read unless there is
   // an error.
   int Read(int64_t offset, char* data, int size);
+  int Read(int64_t offset, base::span<uint8_t> data);
 
   // Same as above but without seek.
   int ReadAtCurrentPos(char* data, int size);
+  int ReadAtCurrentPos(base::span<uint8_t> data);
 
   // Reads the given number of bytes (or until EOF is reached) starting with the
   // given offset, but does not make any effort to read all data on all
@@ -239,9 +241,11 @@
   // Ignores the offset and writes to the end of the file if the file was opened
   // with FLAG_APPEND.
   int Write(int64_t offset, const char* data, int size);
+  int Write(int64_t offset, base::span<const uint8_t> data);
 
   // Save as above but without seek.
   int WriteAtCurrentPos(const char* data, int size);
+  int WriteAtCurrentPos(base::span<const uint8_t> data);
 
   // Save as above but does not make any effort to write all data on all
   // platforms. Returns the number of bytes written, or -1 on error.
diff --git a/base/files/file_path_watcher_unittest.cc b/base/files/file_path_watcher_unittest.cc
index 6727eb0..a10ba93 100644
--- a/base/files/file_path_watcher_unittest.cc
+++ b/base/files/file_path_watcher_unittest.cc
@@ -238,7 +238,7 @@
       ExpectedEventsSinceLastWait::kNone;
 };
 
-class TestDelegateBase : public SupportsWeakPtr<TestDelegateBase> {
+class TestDelegateBase {
  public:
   TestDelegateBase() = default;
   TestDelegateBase(const TestDelegateBase&) = delete;
@@ -250,12 +250,13 @@
       const FilePathWatcher::ChangeInfo& change_info,
       const FilePath& path,
       bool error) = 0;
+  virtual base::WeakPtr<TestDelegateBase> AsWeakPtr() = 0;
 };
 
 // Receives and accumulates notifications from a specific `FilePathWatcher`.
 // This class is not thread safe. All methods must be called from the sequence
 // the instance is constructed on.
-class TestDelegate : public TestDelegateBase {
+class TestDelegate final : public TestDelegateBase {
  public:
   TestDelegate() : id_(g_next_delegate_id.GetNext()) {}
   TestDelegate(const TestDelegate&) = delete;
@@ -276,6 +277,10 @@
     received_events_.emplace_back(std::move(event));
   }
 
+  base::WeakPtr<TestDelegateBase> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   // Gives all in-flight events a chance to arrive, then forgets all events that
   // have been received by this delegate. This method may be a useful reset
   // after performing a file system operation that may result in a variable
@@ -342,6 +347,7 @@
   const size_t id_;
 
   std::list<Event> received_events_ GUARDED_BY_CONTEXT(sequence_checker_);
+  base::WeakPtrFactory<TestDelegateBase> weak_ptr_factory_{this};
 };
 
 }  // namespace
@@ -618,7 +624,7 @@
 
 // Used by the DeleteDuringNotify test below.
 // Deletes the FilePathWatcher when it's notified.
-class Deleter : public TestDelegateBase {
+class Deleter final : public TestDelegateBase {
  public:
   explicit Deleter(OnceClosure done_closure)
       : watcher_(std::make_unique<FilePathWatcher>()),
@@ -638,11 +644,16 @@
     std::move(done_closure_).Run();
   }
 
+  base::WeakPtr<TestDelegateBase> AsWeakPtr() override {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   FilePathWatcher* watcher() const { return watcher_.get(); }
 
  private:
   std::unique_ptr<FilePathWatcher> watcher_;
   OnceClosure done_closure_;
+  base::WeakPtrFactory<Deleter> weak_ptr_factory_{this};
 };
 
 }  // namespace
diff --git a/base/fuchsia/time_zone_data_unittest.cc b/base/fuchsia/time_zone_data_unittest.cc
index a1076b98..c87b09f 100644
--- a/base/fuchsia/time_zone_data_unittest.cc
+++ b/base/fuchsia/time_zone_data_unittest.cc
@@ -23,7 +23,11 @@
 
 // File path to the text file containing the expected ICU library revision, for
 // example "2019c".
+// TODO(crbug.com/1360077): Remove once tzdata is fully migrated away from
+// config-data. I.e. all released Chromium versions in Fuchsia support it.
 const char kRevisionFilePath[] = "/config/data/tzdata/revision.txt";
+// Same as above, except the modern version.
+const char kTZDataRevisionFilePath[] = "/config/tzdata/icu/revision.txt";
 
 }  // namespace
 
@@ -63,8 +67,16 @@
 // that this test is not skipped. In Chromium build bot setup, this file may
 // not be present, in which case we skip running this test.
 TEST_F(TimeZoneDataTest, CompareSystemRevisionWithExpected) {
-  if (!base::PathExists(base::FilePath(kRevisionFilePath))) {
-    GTEST_SKIP() << "Skipped test because tzdata config is not present";
+  std::string revision_file_path;
+  if (base::PathExists(base::FilePath(kTZDataRevisionFilePath))) {
+    revision_file_path = kTZDataRevisionFilePath;
+  } else if (base::PathExists(base::FilePath(kRevisionFilePath))) {
+    // Legacy path.
+    revision_file_path = kRevisionFilePath;
+  }
+
+  if (revision_file_path.empty()) {
+    FAIL() << "No revision file found";
   }
 
   // ResetIcu() ensures that time zone data is loaded from the default location.
@@ -75,7 +87,7 @@
   ASSERT_TRUE(InitializeICU());
   std::string expected;
   EXPECT_TRUE(
-      base::ReadFileToString(base::FilePath(kRevisionFilePath), &expected));
+      base::ReadFileToString(base::FilePath(revision_file_path), &expected));
   std::string actual;
   GetActualRevision(&actual);
   EXPECT_EQ(expected, actual);
diff --git a/base/rand_util.cc b/base/rand_util.cc
index 7b290f4e4..0be3aaa5 100644
--- a/base/rand_util.cc
+++ b/base/rand_util.cc
@@ -115,7 +115,7 @@
 std::vector<uint8_t> RandBytesAsVector(size_t length) {
   std::vector<uint8_t> result(length);
   if (result.size()) {
-    RandBytes(result.data(), result.size());
+    RandBytes(result);
   }
   return result;
 }
diff --git a/base/rand_util.h b/base/rand_util.h
index 8387bc9..00e901a3 100644
--- a/base/rand_util.h
+++ b/base/rand_util.h
@@ -14,6 +14,7 @@
 
 #include "base/base_export.h"
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/gtest_prod_util.h"
 #include "build/build_config.h"
 
@@ -83,11 +84,13 @@
 // [0, 1). Thread-safe.
 BASE_EXPORT float BitsToOpenEndedUnitIntervalF(uint64_t bits);
 
-// Fills |output_length| bytes of |output| with random data. Thread-safe.
+// Fills `output` with random data. Thread-safe.
 //
 // Although implementations are required to use a cryptographically secure
 // random number source, code outside of base/ that relies on this should use
 // crypto::RandBytes instead to ensure the requirement is easily discoverable.
+BASE_EXPORT void RandBytes(span<uint8_t> output);
+// TODO(https://crbug.com/1490484): Migrate callers to the span version.
 BASE_EXPORT void RandBytes(void* output, size_t output_length);
 
 // Creates a vector of `length` bytes, fills it with random data, and returns
diff --git a/base/rand_util_fuchsia.cc b/base/rand_util_fuchsia.cc
index b9e5eaa9..7804634 100644
--- a/base/rand_util_fuchsia.cc
+++ b/base/rand_util_fuchsia.cc
@@ -8,6 +8,7 @@
 
 #include <atomic>
 
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "third_party/boringssl/src/include/openssl/crypto.h"
 #include "third_party/boringssl/src/include/openssl/rand.h"
@@ -39,16 +40,20 @@
 
 }  // namespace internal
 
-void RandBytes(void* output, size_t output_length) {
+void RandBytes(span<uint8_t> output) {
   if (internal::UseBoringSSLForRandBytes()) {
     // Ensure BoringSSL is initialized so it can use things like RDRAND.
     CRYPTO_library_init();
     // BoringSSL's RAND_bytes always returns 1. Any error aborts the program.
-    (void)RAND_bytes(static_cast<uint8_t*>(output), output_length);
+    (void)RAND_bytes(output.data(), output.size());
     return;
   }
 
-  zx_cprng_draw(output, output_length);
+  zx_cprng_draw(output.data(), output.size());
+}
+
+void RandBytes(void* output, size_t output_length) {
+  RandBytes(make_span(reinterpret_cast<uint8_t*>(output), output_length));
 }
 
 namespace internal {
diff --git a/base/rand_util_nacl.cc b/base/rand_util_nacl.cc
index 6ac810e..dfa7263 100644
--- a/base/rand_util_nacl.cc
+++ b/base/rand_util_nacl.cc
@@ -9,19 +9,22 @@
 #include <stdint.h>
 
 #include "base/check_op.h"
+#include "base/containers/span.h"
 
 namespace base {
 
-void RandBytes(void* output, size_t output_length) {
-  char* output_ptr = static_cast<char*>(output);
-  while (output_length > 0) {
+void RandBytes(span<uint8_t> output) {
+  while (!output.empty()) {
     size_t nread;
-    const int error = nacl_secure_random(output_ptr, output_length, &nread);
+    const int error = nacl_secure_random(output.data(), output.size(), &nread);
     CHECK_EQ(error, 0);
-    CHECK_LE(nread, output_length);
-    output_ptr += nread;
-    output_length -= nread;
+    CHECK_LE(nread, output.size());
+    output = output.subspan(nread);
   }
 }
 
+void RandBytes(void* output, size_t output_length) {
+  RandBytes(make_span(reinterpret_cast<uint8_t*>(output), output_length));
+}
+
 }  // namespace base
diff --git a/base/rand_util_posix.cc b/base/rand_util_posix.cc
index 29cd9d0..d0da3d7 100644
--- a/base/rand_util_posix.cc
+++ b/base/rand_util_posix.cc
@@ -14,6 +14,7 @@
 
 #include "base/check.h"
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_macros.h"
@@ -175,14 +176,14 @@
 
 namespace {
 
-void RandBytes(void* output, size_t output_length, bool avoid_allocation) {
+void RandBytes(span<uint8_t> output, bool avoid_allocation) {
 #if !BUILDFLAG(IS_NACL)
   // The BoringSSL experiment takes priority over everything else.
   if (!avoid_allocation && internal::UseBoringSSLForRandBytes()) {
     // Ensure BoringSSL is initialized so it can use things like RDRAND.
     CRYPTO_library_init();
     // BoringSSL's RAND_bytes always returns 1. Any error aborts the program.
-    (void)RAND_bytes(static_cast<uint8_t*>(output), output_length);
+    (void)RAND_bytes(output.data(), output.size());
     return;
   }
 #endif
@@ -194,13 +195,14 @@
     // support for a syscall before calling. The same check is made on Linux and
     // ChromeOS to avoid making a syscall that predictably returns ENOSYS.
     static const bool kernel_has_support = KernelSupportsGetRandom();
-    if (kernel_has_support && GetRandomSyscall(output, output_length))
+    if (kernel_has_support && GetRandomSyscall(output.data(), output.size())) {
       return;
+    }
   }
 #elif BUILDFLAG(IS_MAC)
   // TODO(crbug.com/995996): Enable this on iOS too, when sys/random.h arrives
   // in its SDK.
-  if (getentropy(output, output_length) == 0) {
+  if (getentropy(output.data(), output.size()) == 0) {
     return;
   }
 #endif
@@ -211,8 +213,7 @@
   // TODO(crbug.com/995996): When we no longer need to support old Linux
   // kernels, we can get rid of this /dev/urandom branch altogether.
   const int urandom_fd = GetUrandomFD();
-  const bool success = ReadFromFD(
-      urandom_fd, make_span(static_cast<char*>(output), output_length));
+  const bool success = ReadFromFD(urandom_fd, as_writable_chars(output));
   CHECK(success);
 }
 
@@ -222,15 +223,20 @@
 
 double RandDoubleAvoidAllocation() {
   uint64_t number;
-  RandBytes(&number, sizeof(number), /*avoid_allocation=*/true);
+  RandBytes(as_writable_bytes(make_span(&number, 1u)),
+            /*avoid_allocation=*/true);
   // This transformation is explained in rand_util.cc.
   return (number >> 11) * 0x1.0p-53;
 }
 
 }  // namespace internal
 
+void RandBytes(span<uint8_t> output) {
+  RandBytes(output, /*avoid_allocation=*/false);
+}
+
 void RandBytes(void* output, size_t output_length) {
-  RandBytes(output, output_length, /*avoid_allocation=*/false);
+  RandBytes(make_span(reinterpret_cast<uint8_t*>(output), output_length));
 }
 
 int GetUrandomFD() {
diff --git a/base/rand_util_unittest.cc b/base/rand_util_unittest.cc
index e4b0491..7c731b2 100644
--- a/base/rand_util_unittest.cc
+++ b/base/rand_util_unittest.cc
@@ -13,6 +13,7 @@
 #include <memory>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/time/time.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -121,9 +122,9 @@
 
 TEST(RandUtilTest, RandBytes) {
   const size_t buffer_size = 50;
-  char buffer[buffer_size];
+  uint8_t buffer[buffer_size];
   memset(buffer, 0, buffer_size);
-  base::RandBytes(buffer, buffer_size);
+  base::RandBytes(buffer);
   std::sort(buffer, buffer + buffer_size);
   // Probability of occurrence of less than 25 unique bytes in 50 random bytes
   // is below 10^-25.
@@ -132,6 +133,7 @@
 
 // Verify that calling base::RandBytes with an empty buffer doesn't fail.
 TEST(RandUtilTest, RandBytes0) {
+  base::RandBytes(span<uint8_t>());
   base::RandBytes(nullptr, 0);
 }
 
@@ -256,7 +258,7 @@
   std::unique_ptr<uint8_t[]> buffer(new uint8_t[kTestBufferSize]);
   const base::TimeTicks now = base::TimeTicks::Now();
   for (int i = 0; i < kTestIterations; ++i)
-    base::RandBytes(buffer.get(), kTestBufferSize);
+    base::RandBytes(make_span(buffer.get(), kTestBufferSize));
   const base::TimeTicks end = base::TimeTicks::Now();
 
   LOG(INFO) << "RandBytes(" << kTestBufferSize
diff --git a/base/rand_util_win.cc b/base/rand_util_win.cc
index 549f4362..6aae763 100644
--- a/base/rand_util_win.cc
+++ b/base/rand_util_win.cc
@@ -65,32 +65,39 @@
   return process_prng_fn;
 }
 
-void RandBytes(void* output, size_t output_length, bool avoid_allocation) {
+void RandBytes(span<uint8_t> output, bool avoid_allocation) {
   if (!avoid_allocation && internal::UseBoringSSLForRandBytes()) {
     // Ensure BoringSSL is initialized so it can use things like RDRAND.
     CRYPTO_library_init();
     // BoringSSL's RAND_bytes always returns 1. Any error aborts the program.
-    (void)RAND_bytes(static_cast<uint8_t*>(output), output_length);
+    (void)RAND_bytes(output.data(), output.size());
     return;
   }
 
   static decltype(&ProcessPrng) process_prng_fn = GetProcessPrng();
-  BOOL success = process_prng_fn(static_cast<BYTE*>(output), output_length);
+  BOOL success =
+      process_prng_fn(static_cast<BYTE*>(output.data()), output.size());
   // ProcessPrng is documented to always return TRUE.
   CHECK(success);
 }
 
 }  // namespace
 
+void RandBytes(span<uint8_t> output) {
+  RandBytes(output, /*avoid_allocation=*/false);
+}
+
 void RandBytes(void* output, size_t output_length) {
-  RandBytes(output, output_length, /*avoid_allocation=*/false);
+  RandBytes(make_span(reinterpret_cast<uint8_t*>(output), output_length),
+            /*avoid_allocation=*/false);
 }
 
 namespace internal {
 
 double RandDoubleAvoidAllocation() {
   uint64_t number;
-  RandBytes(&number, sizeof(number), /*avoid_allocation=*/true);
+  RandBytes(as_writable_bytes(make_span(&number, 1u)),
+            /*avoid_allocation=*/true);
   // This transformation is explained in rand_util.cc.
   return (number >> 11) * 0x1.0p-53;
 }
diff --git a/base/stack_canary_linux.cc b/base/stack_canary_linux.cc
index 8fc5297..ace6c11 100644
--- a/base/stack_canary_linux.cc
+++ b/base/stack_canary_linux.cc
@@ -65,7 +65,7 @@
 
 void NO_STACK_PROTECTOR ResetStackCanaryIfPossible() {
   uintptr_t canary;
-  base::RandBytes(&canary, sizeof(canary));
+  base::RandBytes(as_writable_bytes(make_span(&canary, 1u)));
   // First byte should be the null byte for string functions.
   canary &= ~static_cast<uintptr_t>(0xff);
 
diff --git a/base/task/thread_pool/delayed_task_manager.cc b/base/task/thread_pool/delayed_task_manager.cc
index 9971868..7be6bb0 100644
--- a/base/task/thread_pool/delayed_task_manager.cc
+++ b/base/task/thread_pool/delayed_task_manager.cc
@@ -21,13 +21,9 @@
 
 DelayedTaskManager::DelayedTask::DelayedTask() = default;
 
-DelayedTaskManager::DelayedTask::DelayedTask(
-    Task task,
-    PostTaskNowCallback callback,
-    scoped_refptr<TaskRunner> task_runner)
-    : task(std::move(task)),
-      callback(std::move(callback)),
-      task_runner(std::move(task_runner)) {}
+DelayedTaskManager::DelayedTask::DelayedTask(Task task,
+                                             PostTaskNowCallback callback)
+    : task(std::move(task)), callback(std::move(callback)) {}
 
 DelayedTaskManager::DelayedTask::DelayedTask(
     DelayedTaskManager::DelayedTask&& other) = default;
@@ -84,8 +80,7 @@
 
 void DelayedTaskManager::AddDelayedTask(
     Task task,
-    PostTaskNowCallback post_task_now_callback,
-    scoped_refptr<TaskRunner> task_runner) {
+    PostTaskNowCallback post_task_now_callback) {
   DCHECK(task.task);
   DCHECK(!task.delayed_run_time.is_null());
   DCHECK(!task.queue_time.is_null());
@@ -103,9 +98,8 @@
 
     auto [old_process_ripe_tasks_time, old_delay_policy] =
         GetTimeAndDelayPolicyToScheduleProcessRipeTasksLockRequired();
-    delayed_task_queue_.insert(DelayedTask(std::move(task),
-                                           std::move(post_task_now_callback),
-                                           std::move(task_runner)));
+    delayed_task_queue_.insert(
+        DelayedTask(std::move(task), std::move(post_task_now_callback)));
     // Not started or already shutdown.
     if (service_thread_task_runner_ == nullptr)
       return;
diff --git a/base/task/thread_pool/delayed_task_manager.h b/base/task/thread_pool/delayed_task_manager.h
index 79e45d2..d000053 100644
--- a/base/task/thread_pool/delayed_task_manager.h
+++ b/base/task/thread_pool/delayed_task_manager.h
@@ -50,11 +50,8 @@
   void Start(scoped_refptr<SequencedTaskRunner> service_thread_task_runner);
 
   // Schedules a call to |post_task_now_callback| with |task| as argument when
-  // |task| is ripe for execution. |task_runner| is passed to retain a
-  // reference until |task| is ripe.
-  void AddDelayedTask(Task task,
-                      PostTaskNowCallback post_task_now_callback,
-                      scoped_refptr<TaskRunner> task_runner);
+  // |task| is ripe for execution.
+  void AddDelayedTask(Task task, PostTaskNowCallback post_task_now_callback);
 
   // Pop and post all the ripe tasks in the delayed task queue.
   void ProcessRipeTasks();
@@ -73,9 +70,7 @@
  private:
   struct DelayedTask {
     DelayedTask();
-    DelayedTask(Task task,
-                PostTaskNowCallback callback,
-                scoped_refptr<TaskRunner> task_runner);
+    DelayedTask(Task task, PostTaskNowCallback callback);
     DelayedTask(DelayedTask&& other);
     DelayedTask(const DelayedTask&) = delete;
     DelayedTask& operator=(const DelayedTask&) = delete;
@@ -89,7 +84,6 @@
 
     Task task;
     PostTaskNowCallback callback;
-    scoped_refptr<TaskRunner> task_runner;
 
     // Mark the delayed task as scheduled. Since the sort key is
     // |task.delayed_run_time|, it does not alter sort order when it is called.
diff --git a/base/task/thread_pool/delayed_task_manager_unittest.cc b/base/task/thread_pool/delayed_task_manager_unittest.cc
index 70f5fe85..6fa4b6a 100644
--- a/base/task/thread_pool/delayed_task_manager_unittest.cc
+++ b/base/task/thread_pool/delayed_task_manager_unittest.cc
@@ -86,8 +86,8 @@
 // Verify that a delayed task isn't forwarded before Start().
 TEST_F(ThreadPoolDelayedTaskManagerTest, DelayedTaskDoesNotRunBeforeStart) {
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   // Fast-forward time until the task is ripe for execution. Since Start() has
   // not been called, the task should not be forwarded to PostTaskNow()
@@ -101,8 +101,8 @@
 TEST_F(ThreadPoolDelayedTaskManagerTest,
        DelayedTaskPostedBeforeStartExpiresAfterStartRunsOnExpire) {
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   delayed_task_manager_.Start(service_thread_task_runner_);
 
@@ -121,8 +121,8 @@
 TEST_F(ThreadPoolDelayedTaskManagerTest,
        DelayedTaskPostedBeforeStartExpiresBeforeStartRunsOnStart) {
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   // Run tasks on the service thread. Don't expect any forwarding to
   // |task_target_| since the task isn't ripe for execution.
@@ -145,8 +145,8 @@
   delayed_task_manager_.Start(service_thread_task_runner_);
 
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   // Run tasks that are ripe for execution. Don't expect any forwarding to
   // PostTaskNow().
@@ -159,8 +159,8 @@
   delayed_task_manager_.Start(service_thread_task_runner_);
 
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   // Fast-forward time. Expect the task to be forwarded to PostTaskNow().
   EXPECT_CALL(mock_callback_, Run());
@@ -182,8 +182,7 @@
                           base::subtle::DelayPolicy::kFlexiblePreferEarly);
 
   // Send |task| to the DelayedTaskManager.
-  delayed_task_manager_.AddDelayedTask(std::move(task), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task), BindOnce(&PostTaskNow));
 
   // The task isn't forwarded before the earliest run time is reached.
   service_thread_task_runner_->FastForwardBy(kUnalignedLongDelay - kLeeway -
@@ -210,11 +209,11 @@
   auto post_cancelable_task_now = BindLambdaForTesting(
       [&](Task task) { post_cancelable_task_now_invoked = true; });
   delayed_task_manager_.AddDelayedTask(std::move(cancelable_task),
-                                       post_cancelable_task_now, nullptr);
+                                       post_cancelable_task_now);
 
   // Add |task_| to the DelayedTaskManager with a long delay.
-  delayed_task_manager_.AddDelayedTask(std::move(task_), BindOnce(&PostTaskNow),
-                                       nullptr);
+  delayed_task_manager_.AddDelayedTask(std::move(task_),
+                                       BindOnce(&PostTaskNow));
 
   // Cancel the cancelable task.
   cancelable_closure.Cancel();
@@ -247,11 +246,11 @@
 
   // Send tasks to the DelayedTaskManager.
   delayed_task_manager_.AddDelayedTask(std::move(task_a),
-                                       BindOnce(&PostTaskNow), nullptr);
+                                       BindOnce(&PostTaskNow));
   delayed_task_manager_.AddDelayedTask(std::move(task_b),
-                                       BindOnce(&PostTaskNow), nullptr);
+                                       BindOnce(&PostTaskNow));
   delayed_task_manager_.AddDelayedTask(std::move(task_c),
-                                       BindOnce(&PostTaskNow), nullptr);
+                                       BindOnce(&PostTaskNow));
 
   // Run tasks that are ripe for execution on the service thread. Don't expect
   // any call to PostTaskNow().
@@ -290,11 +289,11 @@
 
   // Send tasks to the DelayedTaskManager.
   delayed_task_manager_.AddDelayedTask(std::move(task_a),
-                                       BindOnce(&PostTaskNow), nullptr);
+                                       BindOnce(&PostTaskNow));
   EXPECT_EQ(base::subtle::DelayPolicy::kFlexibleNoSooner,
             delayed_task_manager_.TopTaskDelayPolicyForTesting());
   delayed_task_manager_.AddDelayedTask(std::move(task_b),
-                                       BindOnce(&PostTaskNow), nullptr);
+                                       BindOnce(&PostTaskNow));
   EXPECT_EQ(base::subtle::DelayPolicy::kPrecise,
             delayed_task_manager_.TopTaskDelayPolicyForTesting());
 
@@ -321,8 +320,7 @@
   other_thread.task_runner()->PostTask(FROM_HERE, BindLambdaForTesting([&]() {
                                          delayed_task_manager_.AddDelayedTask(
                                              std::move(task_),
-                                             BindOnce(&PostTaskNow),
-                                             other_thread.task_runner());
+                                             BindOnce(&PostTaskNow));
                                          task_posted.Signal();
                                        }));
 
diff --git a/base/task/thread_pool/job_task_source.cc b/base/task/thread_pool/job_task_source.cc
index 8f1cc1f4..1b70c2e 100644
--- a/base/task/thread_pool/job_task_source.cc
+++ b/base/task/thread_pool/job_task_source.cc
@@ -127,7 +127,7 @@
     RepeatingCallback<void(JobDelegate*)> worker_task,
     MaxConcurrencyCallback max_concurrency_callback,
     PooledTaskRunnerDelegate* delegate)
-    : JobTaskSource(traits, nullptr, TaskSourceExecutionMode::kJob),
+    : JobTaskSource(traits, TaskSourceExecutionMode::kJob),
       max_concurrency_callback_(std::move(max_concurrency_callback)),
       worker_task_(std::move(worker_task)),
       primary_task_(base::BindRepeating(
@@ -154,7 +154,7 @@
 }
 
 ExecutionEnvironment JobTaskSourceNew::GetExecutionEnvironment() {
-  return {SequenceToken::Create(), nullptr};
+  return {SequenceToken::Create()};
 }
 
 void JobTaskSourceNew::WillEnqueue(int sequence_num, TaskAnnotator& annotator) {
diff --git a/base/task/thread_pool/job_task_source_interface.h b/base/task/thread_pool/job_task_source_interface.h
index 7d9a8c3..db65228 100644
--- a/base/task/thread_pool/job_task_source_interface.h
+++ b/base/task/thread_pool/job_task_source_interface.h
@@ -43,9 +43,8 @@
 
  protected:
   JobTaskSource(const TaskTraits& traits,
-                TaskRunner* task_runner,
                 TaskSourceExecutionMode execution_mode)
-      : TaskSource(traits, task_runner, execution_mode) {}
+      : TaskSource(traits, execution_mode) {}
   ~JobTaskSource() override = default;
 };
 
diff --git a/base/task/thread_pool/job_task_source_old.cc b/base/task/thread_pool/job_task_source_old.cc
index 1857fe8..5fb4856b 100644
--- a/base/task/thread_pool/job_task_source_old.cc
+++ b/base/task/thread_pool/job_task_source_old.cc
@@ -93,7 +93,7 @@
     RepeatingCallback<void(JobDelegate*)> worker_task,
     MaxConcurrencyCallback max_concurrency_callback,
     PooledTaskRunnerDelegate* delegate)
-    : JobTaskSource(traits, nullptr, TaskSourceExecutionMode::kJob),
+    : JobTaskSource(traits, TaskSourceExecutionMode::kJob),
       max_concurrency_callback_(std::move(max_concurrency_callback)),
       worker_task_(std::move(worker_task)),
       primary_task_(base::BindRepeating(
@@ -116,7 +116,7 @@
 }
 
 ExecutionEnvironment JobTaskSourceOld::GetExecutionEnvironment() {
-  return {SequenceToken::Create(), nullptr};
+  return {SequenceToken::Create()};
 }
 
 void JobTaskSourceOld::WillEnqueue(int sequence_num, TaskAnnotator& annotator) {
diff --git a/base/task/thread_pool/pooled_parallel_task_runner.cc b/base/task/thread_pool/pooled_parallel_task_runner.cc
index 23c18568..30fb11a 100644
--- a/base/task/thread_pool/pooled_parallel_task_runner.cc
+++ b/base/task/thread_pool/pooled_parallel_task_runner.cc
@@ -28,7 +28,7 @@
 
   // Post the task as part of a one-off single-task Sequence.
   scoped_refptr<Sequence> sequence = MakeRefCounted<Sequence>(
-      traits_, this, TaskSourceExecutionMode::kParallel);
+      traits_, nullptr, TaskSourceExecutionMode::kParallel);
 
   return pooled_task_runner_delegate_->PostTaskWithSequence(
       Task(from_here, std::move(closure), TimeTicks::Now(), delay),
diff --git a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
index b89334d..3f73be4 100644
--- a/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
+++ b/base/task/thread_pool/pooled_single_thread_task_runner_manager.cc
@@ -177,7 +177,11 @@
 
   TimeDelta GetSleepTimeout() override { return TimeDelta::Max(); }
 
-  bool PostTaskNow(scoped_refptr<Sequence> sequence, Task task) {
+  // `task_runner` isn't used but is forwarded to keep the task runner
+  // alive while the task is pending.
+  bool PostTaskNow(scoped_refptr<Sequence> sequence,
+                   scoped_refptr<SingleThreadTaskRunner> task_runner,
+                   Task task) {
     auto transaction = sequence->BeginTransaction();
 
     // |task| will be pushed to |sequence|, and |sequence| will be queued
@@ -538,15 +542,15 @@
     }
 
     if (task.delayed_run_time.is_null())
-      return GetDelegate()->PostTaskNow(sequence_, std::move(task));
+      return GetDelegate()->PostTaskNow(sequence_, nullptr, std::move(task));
 
     // Unretained(GetDelegate()) is safe because this TaskRunner and its
     // worker are kept alive as long as there are pending Tasks.
     outer_->delayed_task_manager_->AddDelayedTask(
         std::move(task),
         BindOnce(IgnoreResult(&WorkerThreadDelegate::PostTaskNow),
-                 Unretained(GetDelegate()), sequence_),
-        this);
+                 Unretained(GetDelegate()), sequence_,
+                 base::WrapRefCounted(this)));
     return true;
   }
 
diff --git a/base/task/thread_pool/sequence.cc b/base/task/thread_pool/sequence.cc
index 14c9b8c8c..9ff5e35 100644
--- a/base/task/thread_pool/sequence.cc
+++ b/base/task/thread_pool/sequence.cc
@@ -330,9 +330,9 @@
 }
 
 Sequence::Sequence(const TaskTraits& traits,
-                   TaskRunner* task_runner,
+                   SequencedTaskRunner* task_runner,
                    TaskSourceExecutionMode execution_mode)
-    : TaskSource(traits, task_runner, execution_mode) {}
+    : TaskSource(traits, execution_mode), task_runner_(task_runner) {}
 
 Sequence::~Sequence() = default;
 
@@ -341,7 +341,11 @@
 }
 
 ExecutionEnvironment Sequence::GetExecutionEnvironment() {
-  return {token_, &sequence_local_storage_};
+  if (execution_mode() == TaskSourceExecutionMode::kSingleThread) {
+    return {token_, &sequence_local_storage_,
+            static_cast<SingleThreadTaskRunner*>(task_runner())};
+  }
+  return {token_, &sequence_local_storage_, task_runner()};
 }
 
 bool Sequence::IsEmpty() const {
diff --git a/base/task/thread_pool/sequence.h b/base/task/thread_pool/sequence.h
index 7bbc13b2..c8de9bb6 100644
--- a/base/task/thread_pool/sequence.h
+++ b/base/task/thread_pool/sequence.h
@@ -85,7 +85,7 @@
   // case |execution_mode| must be kParallel. Otherwise, |execution_mode| is the
   // execution mode of |task_runner|.
   Sequence(const TaskTraits& traits,
-           TaskRunner* task_runner,
+           SequencedTaskRunner* task_runner,
            TaskSourceExecutionMode execution_mode);
   Sequence(const Sequence&) = delete;
   Sequence& operator=(const Sequence&) = delete;
@@ -115,6 +115,12 @@
   bool is_immediate_for_testing() const { return is_immediate_; }
   bool IsEmptyForTesting() const NO_THREAD_SAFETY_ANALYSIS { return IsEmpty(); }
 
+  // A reference to TaskRunner is only retained between
+  // PushImmediateTask()/PushDelayedTask() and when DidProcessTask() returns
+  // false, guaranteeing it is safe to dereference this pointer. Otherwise, the
+  // caller should guarantee such TaskRunner still exists before dereferencing.
+  SequencedTaskRunner* task_runner() const { return task_runner_; }
+
  private:
   ~Sequence() override;
 
@@ -160,6 +166,15 @@
 
   const SequenceToken token_ = SequenceToken::Create();
 
+  // A pointer to the TaskRunner that posts to this TaskSource, if any. The
+  // derived class is responsible for calling AddRef() when a TaskSource from
+  // which no Task is executing becomes non-empty and Release() when
+  // it becomes empty again (e.g. when DidProcessTask() returns false).
+  //
+  // In practise, this pointer is going to become dangling. See task_runner()
+  // comment.
+  raw_ptr<SequencedTaskRunner, DisableDanglingPtrDetection> task_runner_;
+
   // Queues of tasks to execute.
   base::queue<Task> queue_ GUARDED_BY(lock_);
   base::IntrusiveHeap<Task, DelayedTaskGreater> delayed_queue_
diff --git a/base/task/thread_pool/task_source.cc b/base/task/thread_pool/task_source.cc
index 0b484c9e..315dcd1 100644
--- a/base/task/thread_pool/task_source.cc
+++ b/base/task/thread_pool/task_source.cc
@@ -15,6 +15,8 @@
 namespace base {
 namespace internal {
 
+ExecutionEnvironment::~ExecutionEnvironment() = default;
+
 TaskSource::Transaction::Transaction(TaskSource* task_source)
     : task_source_(task_source) {
   task_source->lock_.Acquire();
@@ -61,16 +63,10 @@
 }
 
 TaskSource::TaskSource(const TaskTraits& traits,
-                       TaskRunner* task_runner,
                        TaskSourceExecutionMode execution_mode)
     : traits_(traits),
       priority_racy_(traits.priority()),
-      task_runner_(task_runner),
-      execution_mode_(execution_mode) {
-  DCHECK(task_runner_ ||
-         execution_mode_ == TaskSourceExecutionMode::kParallel ||
-         execution_mode_ == TaskSourceExecutionMode::kJob);
-}
+      execution_mode_(execution_mode) {}
 
 TaskSource::~TaskSource() {
   // If this fails, a Transaction was likely held while releasing a reference to
diff --git a/base/task/thread_pool/task_source.h b/base/task/thread_pool/task_source.h
index c6dd977..1d74a97e 100644
--- a/base/task/thread_pool/task_source.h
+++ b/base/task/thread_pool/task_source.h
@@ -35,8 +35,27 @@
 };
 
 struct BASE_EXPORT ExecutionEnvironment {
-  SequenceToken token;
-  raw_ptr<SequenceLocalStorageMap> sequence_local_storage;
+  ExecutionEnvironment(SequenceToken token) : token(token) {}
+
+  ExecutionEnvironment(SequenceToken token,
+                       SequenceLocalStorageMap* sequence_local_storage,
+                       SingleThreadTaskRunner* single_thread_task_runner)
+      : token(token),
+        sequence_local_storage(sequence_local_storage),
+        single_thread_task_runner(single_thread_task_runner) {}
+
+  ExecutionEnvironment(SequenceToken token,
+                       SequenceLocalStorageMap* sequence_local_storage,
+                       SequencedTaskRunner* sequenced_task_runner)
+      : token(token),
+        sequence_local_storage(sequence_local_storage),
+        sequenced_task_runner(sequenced_task_runner) {}
+  ~ExecutionEnvironment();
+
+  const SequenceToken token;
+  const raw_ptr<SequenceLocalStorageMap> sequence_local_storage;
+  const raw_ptr<SingleThreadTaskRunner> single_thread_task_runner;
+  const raw_ptr<SequencedTaskRunner> sequenced_task_runner;
 };
 
 // A TaskSource is a virtual class that provides a series of Tasks that must be
@@ -137,12 +156,7 @@
   };
 
   // |traits| is metadata that applies to all Tasks in the TaskSource.
-  // |task_runner| is a reference to the TaskRunner feeding this TaskSource.
-  // |task_runner| can be nullptr only for tasks with no TaskRunner, in which
-  // case |execution_mode| must be kParallel. Otherwise, |execution_mode| is the
-  // execution mode of |task_runner|.
   TaskSource(const TaskTraits& traits,
-             TaskRunner* task_runner,
              TaskSourceExecutionMode execution_mode);
   TaskSource(const TaskSource&) = delete;
   TaskSource& operator=(const TaskSource&) = delete;
@@ -201,12 +215,6 @@
   // Transaction because it is never mutated.
   ThreadPolicy thread_policy() const { return traits_.thread_policy(); }
 
-  // A reference to TaskRunner is only retained between
-  // PushImmediateTask()/PushDelayedTask() and when DidProcessTask() returns
-  // false, guaranteeing it is safe to dereference this pointer. Otherwise, the
-  // caller should guarantee such TaskRunner still exists before dereferencing.
-  TaskRunner* task_runner() const { return task_runner_; }
-
   TaskSourceExecutionMode execution_mode() const { return execution_mode_; }
 
   void ClearForTesting();
@@ -253,15 +261,6 @@
   // protected by the DelayedPriorityQueue's lock.
   HeapHandle delayed_pq_heap_handle_;
 
-  // A pointer to the TaskRunner that posts to this TaskSource, if any. The
-  // derived class is responsible for calling AddRef() when a TaskSource from
-  // which no Task is executing becomes non-empty and Release() when
-  // it becomes empty again (e.g. when DidProcessTask() returns false).
-  //
-  // In practise, this pointer is going to become dangling. See task_runner()
-  // comment.
-  raw_ptr<TaskRunner, DisableDanglingPtrDetection> task_runner_;
-
   TaskSourceExecutionMode execution_mode_;
 };
 
diff --git a/base/task/thread_pool/task_tracker.cc b/base/task/thread_pool/task_tracker.cc
index edcbc19..88ca7092 100644
--- a/base/task/thread_pool/task_tracker.cc
+++ b/base/task/thread_pool/task_tracker.cc
@@ -408,6 +408,7 @@
   }
   if (should_run_tasks)
     AfterRunTask(task_source->shutdown_behavior());
+
   const bool task_source_must_be_queued = task_source.DidProcessTask();
   // |task_source| should be reenqueued iff requested by DidProcessTask().
   if (task_source_must_be_queued)
@@ -474,20 +475,21 @@
         sequenced_task_runner_current_default_handle;
     absl::optional<SingleThreadTaskRunner::CurrentDefaultHandle>
         single_thread_task_runner_current_default_handle;
-    switch (task_source->execution_mode()) {
-      case TaskSourceExecutionMode::kJob:
-      case TaskSourceExecutionMode::kParallel:
-        break;
-      case TaskSourceExecutionMode::kSequenced:
-        DCHECK(task_source->task_runner());
-        sequenced_task_runner_current_default_handle.emplace(
-            static_cast<SequencedTaskRunner*>(task_source->task_runner()));
-        break;
-      case TaskSourceExecutionMode::kSingleThread:
-        DCHECK(task_source->task_runner());
-        single_thread_task_runner_current_default_handle.emplace(
-            static_cast<SingleThreadTaskRunner*>(task_source->task_runner()));
-        break;
+    if (environment.sequenced_task_runner) {
+      DCHECK_EQ(TaskSourceExecutionMode::kSequenced,
+                task_source->execution_mode());
+      sequenced_task_runner_current_default_handle.emplace(
+          environment.sequenced_task_runner.get());
+    } else if (environment.single_thread_task_runner) {
+      DCHECK_EQ(TaskSourceExecutionMode::kSingleThread,
+                task_source->execution_mode());
+      single_thread_task_runner_current_default_handle.emplace(
+          environment.single_thread_task_runner.get());
+    } else {
+      DCHECK_NE(TaskSourceExecutionMode::kSequenced,
+                task_source->execution_mode());
+      DCHECK_NE(TaskSourceExecutionMode::kSingleThread,
+                task_source->execution_mode());
     }
 
     RunTaskWithShutdownBehavior(task, traits, task_source, environment.token);
diff --git a/base/task/thread_pool/task_tracker_unittest.cc b/base/task/thread_pool/task_tracker_unittest.cc
index d2795a98..729a712 100644
--- a/base/task/thread_pool/task_tracker_unittest.cc
+++ b/base/task/thread_pool/task_tracker_unittest.cc
@@ -540,7 +540,7 @@
     TaskTracker* tracker,
     Task verify_task,
     TaskTraits traits,
-    scoped_refptr<TaskRunner> task_runner,
+    scoped_refptr<SequencedTaskRunner> task_runner,
     TaskSourceExecutionMode execution_mode) {
   // Pretend |verify_task| is posted to respect TaskTracker's contract.
   EXPECT_TRUE(tracker->WillPostTask(&verify_task, traits.shutdown_behavior()));
diff --git a/base/task/thread_pool/test_utils.cc b/base/task/thread_pool/test_utils.cc
index f3c1e78c..effc1ed 100644
--- a/base/task/thread_pool/test_utils.cc
+++ b/base/task/thread_pool/test_utils.cc
@@ -106,7 +106,7 @@
 scoped_refptr<Sequence> CreateSequenceWithTask(
     Task task,
     const TaskTraits& traits,
-    scoped_refptr<TaskRunner> task_runner,
+    scoped_refptr<SequencedTaskRunner> task_runner,
     TaskSourceExecutionMode execution_mode) {
   scoped_refptr<Sequence> sequence =
       MakeRefCounted<Sequence>(traits, task_runner.get(), execution_mode);
@@ -186,12 +186,12 @@
         std::move(task),
         BindOnce(
             [](scoped_refptr<Sequence> sequence,
-               MockPooledTaskRunnerDelegate* self, Task task) {
+               MockPooledTaskRunnerDelegate* self,
+               scoped_refptr<TaskRunner> task_runner, Task task) {
               self->PostTaskWithSequenceNow(std::move(task),
                                             std::move(sequence));
             },
-            std::move(sequence), Unretained(this)),
-        std::move(task_runner));
+            std::move(sequence), Unretained(this), std::move(task_runner)));
   }
 
   return true;
diff --git a/base/task/thread_pool/test_utils.h b/base/task/thread_pool/test_utils.h
index 78eac67..94311134 100644
--- a/base/task/thread_pool/test_utils.h
+++ b/base/task/thread_pool/test_utils.h
@@ -122,7 +122,7 @@
 scoped_refptr<Sequence> CreateSequenceWithTask(
     Task task,
     const TaskTraits& traits,
-    scoped_refptr<TaskRunner> task_runner = nullptr,
+    scoped_refptr<SequencedTaskRunner> task_runner = nullptr,
     TaskSourceExecutionMode execution_mode =
         TaskSourceExecutionMode::kParallel);
 
diff --git a/base/task/thread_pool/thread_pool_impl.cc b/base/task/thread_pool/thread_pool_impl.cc
index cea0c79..9606312 100644
--- a/base/task/thread_pool/thread_pool_impl.cc
+++ b/base/task/thread_pool/thread_pool_impl.cc
@@ -445,12 +445,12 @@
         std::move(task),
         BindOnce(
             [](scoped_refptr<Sequence> sequence,
-               ThreadPoolImpl* thread_pool_impl, Task task) {
+               ThreadPoolImpl* thread_pool_impl, scoped_refptr<TaskRunner>,
+               Task task) {
               thread_pool_impl->PostTaskWithSequenceNow(std::move(task),
                                                         std::move(sequence));
             },
-            std::move(sequence), Unretained(this)),
-        std::move(task_runner));
+            std::move(sequence), Unretained(this), std::move(task_runner)));
   }
 
   return true;
diff --git a/base/timer/mock_timer_unittest.cc b/base/timer/mock_timer_unittest.cc
index 1c05bd3..1c2d6b9 100644
--- a/base/timer/mock_timer_unittest.cc
+++ b/base/timer/mock_timer_unittest.cc
@@ -64,7 +64,7 @@
   EXPECT_EQ(1, calls);
 }
 
-class HasWeakPtr : public base::SupportsWeakPtr<HasWeakPtr> {
+class HasWeakPtr {
  public:
   HasWeakPtr() = default;
 
@@ -72,6 +72,13 @@
   HasWeakPtr& operator=(const HasWeakPtr&) = delete;
 
   virtual ~HasWeakPtr() = default;
+
+  base::WeakPtr<HasWeakPtr> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<HasWeakPtr> weak_ptr_factory_{this};
 };
 
 TEST(MockTimerTest, DoesNotRetainClosure) {
diff --git a/base/tracing/protos/chrome_track_event.proto b/base/tracing/protos/chrome_track_event.proto
index a4ababe3..c2492dd 100644
--- a/base/tracing/protos/chrome_track_event.proto
+++ b/base/tracing/protos/chrome_track_event.proto
@@ -749,7 +749,7 @@
     TASK_TYPE_INTERNAL_NAVIGATION_CANCELLATION = 80;
     TASK_TYPE_LOW_PRIORITY_SCRIPT_EXECUTION = 81;
     TASK_TYPE_STORAGE = 82;
-    TASK_TYPE_NETWORKING_UNFREEZABLE_IMAGE_LOADING = 83;
+    TASK_TYPE_NETWORKING_UNFREEZABLE_RENDER_BLOCKING_LOADING = 83;
     TASK_TYPE_MAIN_THREAD_TASK_QUEUE_V8_LOW_PRIORITY = 84;
     TASK_TYPE_CLIPBOARD = 85;
   }
diff --git a/base/uuid.cc b/base/uuid.cc
index 156dffa5..84173211 100644
--- a/base/uuid.cc
+++ b/base/uuid.cc
@@ -9,6 +9,7 @@
 
 #include <ostream>
 
+#include "base/containers/span.h"
 #include "base/hash/hash.h"
 #include "base/rand_util.h"
 #include "base/strings/string_util.h"
@@ -67,7 +68,7 @@
   uint8_t sixteen_bytes[kGuidV4InputLength];
   // Use base::RandBytes instead of crypto::RandBytes, because crypto calls the
   // base version directly, and to prevent the dependency from base/ to crypto/.
-  RandBytes(&sixteen_bytes, sizeof(sixteen_bytes));
+  RandBytes(sixteen_bytes);
   return FormatRandomDataAsV4Impl(sixteen_bytes);
 }
 
diff --git a/build/config/fuchsia/test/context_provider.shard.test-cml b/build/config/fuchsia/test/context_provider.shard.test-cml
index a1bf40a..e5db2f1f 100644
--- a/build/config/fuchsia/test/context_provider.shard.test-cml
+++ b/build/config/fuchsia/test/context_provider.shard.test-cml
@@ -26,10 +26,5 @@
             from: "parent",
             to: "#context_provider",
         },
-        {
-              directory: "tzdata-icu",
-              from: "parent",
-              to: "#context_provider",
-        },
     ],
 }
diff --git a/build/install-build-deps.py b/build/install-build-deps.py
index a38ad22e..3d3cdaf 100755
--- a/build/install-build-deps.py
+++ b/build/install-build-deps.py
@@ -374,8 +374,6 @@
       "libxrender1",
       "libxtst6",
       "x11-utils",
-      "xserver-xorg-core",  # TODO(crbug.com/1417069): Experimental.
-      "xserver-xorg-video-dummy",  # TODO(crbug.com/1417069): Experimental.
       "xvfb",
       "zlib1g",
   ]
diff --git a/cc/layers/heads_up_display_layer_impl.cc b/cc/layers/heads_up_display_layer_impl.cc
index c7d3283..cb2f808 100644
--- a/cc/layers/heads_up_display_layer_impl.cc
+++ b/cc/layers/heads_up_display_layer_impl.cc
@@ -9,10 +9,10 @@
 
 #include <algorithm>
 #include <iomanip>
+#include <optional>
 #include <utility>
 #include <vector>
 
-#include <optional>
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/shared_memory_mapping.h"
@@ -53,6 +53,7 @@
 #include "gpu/command_buffer/common/shared_image_trace_utils.h"
 #include "gpu/command_buffer/common/shared_image_usage.h"
 #include "gpu/config/gpu_feature_info.h"
+#include "gpu/ipc/client/client_shared_image_interface.h"
 #include "skia/ext/legacy_display_globals.h"
 #include "third_party/skia/include/core/SkFont.h"
 #include "third_party/skia/include/core/SkPaint.h"
@@ -61,6 +62,7 @@
 #include "third_party/skia/include/core/SkTextBlob.h"
 #include "third_party/skia/include/core/SkTypeface.h"
 #include "third_party/skia/include/gpu/GrDirectContext.h"
+#include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/size_conversions.h"
@@ -176,7 +178,14 @@
 class HudSoftwareBacking : public ResourcePool::SoftwareBacking {
  public:
   ~HudSoftwareBacking() override {
-    layer_tree_frame_sink->DidDeleteSharedBitmap(shared_bitmap_id);
+    if (shared_image) {
+      auto* sii = layer_tree_frame_sink->shared_image_interface();
+      if (sii) {
+        sii->DestroySharedImage(mailbox_sync_token, std::move(shared_image));
+      }
+    } else {
+      layer_tree_frame_sink->DidDeleteSharedBitmap(shared_bitmap_id);
+    }
   }
 
   void OnMemoryDump(
@@ -321,23 +330,58 @@
   } else {
     DCHECK_EQ(draw_mode, DRAW_MODE_SOFTWARE);
 
-    pool_resource = pool_->AcquireResource(internal_content_bounds_,
-                                           viz::SinglePlaneFormat::kRGBA_8888,
-                                           gfx::ColorSpace());
+    auto* sii = layer_tree_frame_sink->shared_image_interface();
+    if (sii) {
+      pool_resource = pool_->AcquireResource(internal_content_bounds_,
+                                             viz::SinglePlaneFormat::kBGRA_8888,
+                                             gfx::ColorSpace());
 
-    if (!pool_resource.software_backing()) {
-      auto backing = std::make_unique<HudSoftwareBacking>();
-      backing->layer_tree_frame_sink = layer_tree_frame_sink;
-      backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
-      base::MappedReadOnlyRegion shm =
-          viz::bitmap_allocation::AllocateSharedBitmap(pool_resource.size(),
-                                                       pool_resource.format());
-      backing->shared_mapping = std::move(shm.mapping);
+      if (!pool_resource.software_backing()) {
+        auto backing = std::make_unique<HudSoftwareBacking>();
+        backing->layer_tree_frame_sink = layer_tree_frame_sink;
 
-      layer_tree_frame_sink->DidAllocateSharedBitmap(std::move(shm.region),
-                                                     backing->shared_bitmap_id);
+        const size_t buffer_size = gfx::BufferSizeForBufferFormat(
+            pool_resource.size(), gfx::BufferFormat::BGRA_8888);
+        auto shared_memory_region =
+            base::UnsafeSharedMemoryRegion::Create(buffer_size);
+        backing->shared_mapping = shared_memory_region.Map();
+        CHECK(shared_memory_region.IsValid() &&
+              backing->shared_mapping.IsValid());
 
-      pool_resource.set_software_backing(std::move(backing));
+        gfx::GpuMemoryBufferHandle handle;
+        handle.type = gfx::SHARED_MEMORY_BUFFER;
+        handle.offset = 0;
+        handle.stride = static_cast<int32_t>(gfx::RowSizeForBufferFormat(
+            pool_resource.size().width(), gfx::BufferFormat::BGRA_8888, 0));
+        handle.region = std::move(shared_memory_region);
+
+        backing->shared_image = sii->CreateSharedImage(
+            pool_resource.format(), pool_resource.size(),
+            pool_resource.color_space(), kTopLeft_GrSurfaceOrigin,
+            kPremul_SkAlphaType, gpu::SHARED_IMAGE_USAGE_CPU_WRITE,
+            "HeadsUpDisplayLayer", std::move(handle));
+        CHECK(backing->shared_image);
+
+        pool_resource.set_software_backing(std::move(backing));
+      }
+
+    } else {
+      pool_resource = pool_->AcquireResource(internal_content_bounds_,
+                                             viz::SinglePlaneFormat::kRGBA_8888,
+                                             gfx::ColorSpace());
+      if (!pool_resource.software_backing()) {
+        auto backing = std::make_unique<HudSoftwareBacking>();
+        backing->layer_tree_frame_sink = layer_tree_frame_sink;
+        backing->shared_bitmap_id = viz::SharedBitmap::GenerateId();
+        base::MappedReadOnlyRegion shm =
+            viz::bitmap_allocation::AllocateSharedBitmap(
+                pool_resource.size(), pool_resource.format());
+        backing->shared_mapping = std::move(shm.mapping);
+
+        layer_tree_frame_sink->DidAllocateSharedBitmap(
+            std::move(shm.region), backing->shared_bitmap_id);
+        pool_resource.set_software_backing(std::move(backing));
+      }
     }
   }
 
@@ -421,6 +465,12 @@
 
     SkiaPaintCanvas canvas(surface->getCanvas());
     DrawHudContents(&canvas);
+
+    if (backing->shared_image) {
+      backing->mailbox_sync_token =
+          layer_tree_frame_sink->shared_image_interface()
+              ->GenVerifiedSyncToken();
+    }
   }
 
   // Exports the backing to the ResourceProvider, giving it a ResourceId that
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageService.java
index 0fb55ad..5a6e57e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageService.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageService.java
@@ -355,8 +355,7 @@
      * A method which gets fired when the re-authentication was successful after the review action.
      */
     private void onAfterReviewActionSuccessful() {
-        UserPrefs.get(Profile.getLastUsedRegularProfile())
-                .setBoolean(Pref.INCOGNITO_REAUTHENTICATION_FOR_ANDROID, true);
+        UserPrefs.get(mProfile).setBoolean(Pref.INCOGNITO_REAUTHENTICATION_FOR_ANDROID, true);
         RecordHistogram.recordEnumeratedHistogram(
                 "Android.IncognitoReauth.PromoAcceptedOrDismissed",
                 IncognitoReauthPromoActionType.PROMO_ACCEPTED,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPane.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPane.java
index 0f6c34d..1aa1271 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPane.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPane.java
@@ -17,6 +17,7 @@
 import org.chromium.chrome.browser.hub.FullButtonData;
 import org.chromium.chrome.browser.hub.HubColorScheme;
 import org.chromium.chrome.browser.hub.Pane;
+import org.chromium.chrome.browser.hub.PaneHubController;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.hub.ResourceButtonData;
 import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
@@ -40,6 +41,13 @@
                 @Override
                 public void didBecomeEmpty() {
                     mReferenceButtonDataSupplier.set(null);
+                    if (isFocused()) {
+                        @Nullable PaneHubController controller = getPaneHubController();
+                        assert controller != null
+                                : "isFocused requires a non-null PaneHubController.";
+                        controller.focusPane(PaneId.TAB_SWITCHER);
+                    }
+                    resetWithTabList(null, false);
                 }
             };
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPaneUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPaneUnitTest.java
index b34a96f..ec657254 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPaneUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoTabSwitcherPaneUnitTest.java
@@ -40,11 +40,13 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.shadows.ShadowLooper;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.hub.DisplayButtonData;
 import org.chromium.chrome.browser.hub.FullButtonData;
 import org.chromium.chrome.browser.hub.LoadHint;
+import org.chromium.chrome.browser.hub.PaneHubController;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthController;
 import org.chromium.chrome.browser.incognito.reauth.IncognitoReauthManager.IncognitoReauthCallback;
@@ -68,9 +70,11 @@
     @Mock private TabModelFilter mTabModelFilter;
     @Mock private IncognitoTabModel mIncognitoTabModel;
     @Mock private MenuOrKeyboardActionController mMenuOrKeyboardActionController;
+    @Mock private PaneHubController mPaneHubController;
 
     @Captor private ArgumentCaptor<IncognitoTabModelObserver> mIncognitoTabModelObserverCaptor;
     @Captor private ArgumentCaptor<IncognitoReauthCallback> mIncognitoReauthCallbackCaptor;
+    @Captor private ArgumentCaptor<Callback<Integer>> mOnTabClickedCallbackCaptor;
 
     private final OneshotSupplierImpl<IncognitoReauthController>
             mIncognitoReauthControllerSupplier = new OneshotSupplierImpl<>();
@@ -88,7 +92,13 @@
                             return mTabSwitcherPaneCoordinator;
                         })
                 .when(mTabSwitcherPaneCoordinatorFactory)
-                .create(any(), any(), any(), any(), anyBoolean());
+                .create(
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        mOnTabClickedCallbackCaptor.capture(),
+                        anyBoolean());
 
         when(mTabModelFilter.getTabModel()).thenReturn(mIncognitoTabModel);
 
@@ -302,10 +312,17 @@
     }
 
     private void checkIncognitoTabModelObserverAndButtonData() {
+        mIncognitoTabSwitcherPane.createTabSwitcherPaneCoordinator();
+        TabSwitcherPaneCoordinator coordinator =
+                mIncognitoTabSwitcherPane.getTabSwitcherPaneCoordinator();
+        mIncognitoTabSwitcherPane.setPaneHubController(mPaneHubController);
+
         IncognitoTabModelObserver observer = mIncognitoTabModelObserverCaptor.getValue();
 
         observer.didBecomeEmpty();
         assertNull(mIncognitoTabSwitcherPane.getReferenceButtonDataSupplier().get());
+        verify(coordinator).resetWithTabList(null);
+        verify(mPaneHubController).focusPane(PaneId.TAB_SWITCHER);
 
         // TODO(crbug/1505772): These resources need to be updated.
         observer.wasFirstTabCreated();
@@ -321,5 +338,7 @@
 
         observer.didBecomeEmpty();
         assertNull(mIncognitoTabSwitcherPane.getReferenceButtonDataSupplier().get());
+        verify(coordinator, times(2)).resetWithTabList(null);
+        verify(mPaneHubController, times(2)).focusPane(PaneId.TAB_SWITCHER);
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageService.java
index 2fd2c79..31e5067 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageService.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageService.java
@@ -55,10 +55,9 @@
         }
     }
 
-    IphMessageService(TabSwitcherIphController controller) {
+    IphMessageService(Profile profile, TabSwitcherIphController controller) {
         super(MessageType.IPH);
         mIphController = controller;
-        Profile profile = Profile.getLastUsedRegularProfile().getOriginalProfile();
         mTracker = TrackerFactory.getTrackerForProfile(profile);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageService.java
index 22a2b29..27c7d69 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageService.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageService.java
@@ -154,6 +154,7 @@
                     ? 2
                     : 1;
 
+    private final Profile mProfile;
     private final PriceWelcomeMessageProvider mPriceWelcomeMessageProvider;
     private final PriceWelcomeMessageReviewActionProvider mPriceWelcomeMessageReviewActionProvider;
     private final PriceDropNotificationManager mNotificationManager;
@@ -161,10 +162,12 @@
     private PriceTabData mPriceTabData;
 
     PriceMessageService(
+            Profile profile,
             PriceWelcomeMessageProvider priceWelcomeMessageProvider,
             PriceWelcomeMessageReviewActionProvider priceWelcomeMessageReviewActionProvider,
             PriceDropNotificationManager notificationManager) {
         super(MessageType.PRICE_MESSAGE);
+        mProfile = profile;
         mPriceTabData = null;
         mPriceWelcomeMessageProvider = priceWelcomeMessageProvider;
         mPriceWelcomeMessageReviewActionProvider = priceWelcomeMessageReviewActionProvider;
@@ -176,11 +179,9 @@
      */
     boolean preparePriceMessage(@PriceMessageType int type, @Nullable PriceTabData priceTabData) {
         assert (type == PriceMessageType.PRICE_WELCOME
-                        && PriceTrackingUtilities.isPriceWelcomeMessageCardEnabled(
-                                Profile.getLastUsedRegularProfile()))
+                        && PriceTrackingUtilities.isPriceWelcomeMessageCardEnabled(mProfile))
                 || (type == PriceMessageType.PRICE_ALERTS
-                        && PriceTrackingUtilities.isPriceAlertsMessageCardEnabled(
-                                Profile.getLastUsedRegularProfile()));
+                        && PriceTrackingUtilities.isPriceAlertsMessageCardEnabled(mProfile));
         if (type == PriceMessageType.PRICE_WELCOME) {
             PriceTrackingUtilities.increasePriceWelcomeMessageCardShowCount();
             if (PriceTrackingUtilities.getPriceWelcomeMessageCardShowCount()
@@ -193,8 +194,7 @@
             // When PriceWelcomeMessageCard is available, it takes priority over
             // PriceAlertsMessageCard which will be removed first. This should be called only if
             // PriceAlertsMessageCard is currently enabled.
-            if (PriceTrackingUtilities.isPriceAlertsMessageCardEnabled(
-                    Profile.getLastUsedRegularProfile())) {
+            if (PriceTrackingUtilities.isPriceAlertsMessageCardEnabled(mProfile)) {
                 PriceTrackingUtilities.decreasePriceAlertsMessageCardShowCount();
             }
         } else if (type == PriceMessageType.PRICE_ALERTS) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorBookmarkAction.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorBookmarkAction.java
index 4809e2e..12235fed 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorBookmarkAction.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorBookmarkAction.java
@@ -80,8 +80,10 @@
         @Override
         public void bookmarkTabsAndShowSnackbar(
                 Activity activity, List<Tab> tabs, SnackbarManager snackbarManager) {
-            BookmarkModel bookmarkModel =
-                    BookmarkModel.getForProfile(Profile.getLastUsedRegularProfile());
+            if (tabs.isEmpty()) return;
+
+            Profile profile = tabs.get(0).getProfile();
+            BookmarkModel bookmarkModel = BookmarkModel.getForProfile(profile);
             bookmarkModel.finishLoadingBookmarkModel(
                     () -> {
                         BookmarkUtils.addBookmarksOnMultiSelect(
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageService.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageService.java
index d3a70012..8b2c8ae 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageService.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageService.java
@@ -52,17 +52,14 @@
     public class TabSuggestionMessageData implements MessageData {
         private final TabSuggestion mTabSuggestion;
         private final Callback<TabSuggestionFeedback> mTabSuggestionFeedback;
-        private Profile mProfile;
         private CustomMessageCardProvider mCustomMessageCardProvider;
 
         public TabSuggestionMessageData(
                 TabSuggestion tabSuggestion,
                 Callback<TabSuggestionFeedback> feedbackCallback,
-                Profile profile,
                 CustomMessageCardProvider customMessageCardProvider) {
             mTabSuggestion = tabSuggestion;
             mTabSuggestionFeedback = feedbackCallback;
-            mProfile = profile;
             mCustomMessageCardProvider = customMessageCardProvider;
         }
 
@@ -123,6 +120,7 @@
     }
 
     private final Context mContext;
+    private final Profile mProfile;
     private final Supplier<TabModelFilter> mCurrentTabModelFilterSupplier;
     private final Supplier<TabListEditorCoordinator.TabListEditorController>
             mTabListEditorControllerSupplier;
@@ -132,11 +130,13 @@
 
     public TabSuggestionMessageService(
             Context context,
+            Profile profile,
             Supplier<TabModelFilter> currentTabModelFilterSupplier,
             Supplier<TabListEditorCoordinator.TabListEditorController>
                     tabListEditorControllerSupplier) {
         super(MessageType.TAB_SUGGESTION);
         mContext = context;
+        mProfile = profile;
         mCurrentTabModelFilterSupplier = currentTabModelFilterSupplier;
         mTabListEditorControllerSupplier = tabListEditorControllerSupplier;
         mCustomMessageCardProvider = this;
@@ -291,7 +291,6 @@
                     new TabSuggestionMessageData(
                             tabSuggestion,
                             tabSuggestionFeedback,
-                            Profile.getLastUsedRegularProfile(),
                             mCustomMessageCardProvider));
         }
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
index 07e8773d..26d3407 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherMessageManager.java
@@ -213,6 +213,7 @@
             mTabSuggestionMessageService =
                     new TabSuggestionMessageService(
                             mContext,
+                            profile,
                             mCurrentTabModelFilterSupplier,
                             mTabListEditorControllerSupplier::get);
             mTabSuggestionsOrchestrator.addObserver(mTabSuggestionMessageService);
@@ -221,7 +222,8 @@
 
         mTabGridIphDialogCoordinator =
                 new TabGridIphDialogCoordinator(mContext, mContainer, mModalDialogManager);
-        IphMessageService iphMessageService = new IphMessageService(mTabGridIphDialogCoordinator);
+        IphMessageService iphMessageService =
+                new IphMessageService(profile, mTabGridIphDialogCoordinator);
         mMessageCardProviderCoordinator.subscribeMessageService(iphMessageService);
 
         if (IncognitoReauthManager.isIncognitoReauthFeatureAvailable()
@@ -476,6 +478,7 @@
             if (mPriceMessageService == null) {
                 mPriceMessageService =
                         new PriceMessageService(
+                                mProfile,
                                 mTabListCoordinator,
                                 mPriceWelcomeMessageReviewActionProvider,
                                 notificationManager);
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
index 08bccbeb..bbc60ed 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneBase.java
@@ -31,6 +31,7 @@
 import org.chromium.chrome.browser.hub.HubLayoutConstants;
 import org.chromium.chrome.browser.hub.LoadHint;
 import org.chromium.chrome.browser.hub.Pane;
+import org.chromium.chrome.browser.hub.PaneHubController;
 import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
 import org.chromium.chrome.browser.tasks.tab_management.TabListCoordinator.TabListMode;
 import org.chromium.chrome.tab_ui.R;
@@ -88,6 +89,7 @@
     private final boolean mIsIncognito;
 
     private boolean mNativeInitialized;
+    private @Nullable PaneHubController mPaneHubController;
 
     /**
      * @param context The activity context.
@@ -122,11 +124,19 @@
     }
 
     @Override
+    public void setPaneHubController(@Nullable PaneHubController paneHubController) {
+        mPaneHubController = paneHubController;
+        if (mPaneHubController != null) {
+            mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(
+                    mMenuOrKeyboardActionHandler);
+        } else {
+            mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(
+                    mMenuOrKeyboardActionHandler);
+        }
+    }
+
+    @Override
     public void notifyLoadHint(@LoadHint int loadHint) {
-        // TODO(crbug/1502201): Figure out a more immediate signal for pane visibility. Due to
-        // WARM/COLD signals being posted this can lead to multiple HOT panes for a brief period.
-        // In this case multiple HOT panes might listen for the same menu event leading to a
-        // collision.
         boolean isVisible = loadHint == LoadHint.HOT;
         mIsVisibleSupplier.set(isVisible);
 
@@ -134,8 +144,6 @@
 
         if (isVisible) {
             createTabSwitcherPaneCoordinator();
-            mMenuOrKeyboardActionController.registerMenuOrKeyboardActionHandler(
-                    mMenuOrKeyboardActionHandler);
             showAllTabs();
             setInitialScrollIndexOffset();
             // TODO(crbug/1502201): This should only happen when the Pane becomes user visible which
@@ -144,9 +152,6 @@
             // need to know an animation is going to play and when it is finished (possibly using
             // the isAnimatingSupplier?).
             requestAccessibilityFocusOnCurrentTab();
-        } else {
-            mMenuOrKeyboardActionController.unregisterMenuOrKeyboardActionHandler(
-                    mMenuOrKeyboardActionHandler);
         }
 
         if (loadHint == LoadHint.WARM) {
@@ -180,7 +185,7 @@
     public @NonNull HubLayoutAnimatorProvider createShowHubLayoutAnimatorProvider(
             @NonNull HubContainerView hubContainerView) {
         assert !DeviceFormFactor.isNonMultiDisplayContextOnTablet(hubContainerView.getContext());
-        // TODO(crbug/1505772): Replace with shrink animator and set animating supplier.
+        // TODO(crbug/1516949): Replace with shrink animator and set animating supplier.
         return FadeHubLayoutAnimationFactory.createFadeInAnimatorProvider(
                 hubContainerView, HubLayoutConstants.FADE_DURATION_MS);
     }
@@ -189,7 +194,7 @@
     public @NonNull HubLayoutAnimatorProvider createHideHubLayoutAnimatorProvider(
             @NonNull HubContainerView hubContainerView) {
         assert !DeviceFormFactor.isNonMultiDisplayContextOnTablet(hubContainerView.getContext());
-        // TODO(crbug/1505772): Replace with expand animator and set animating supplier.
+        // TODO(crbug/1516949): Replace with expand animator and set animating supplier.
         return FadeHubLayoutAnimationFactory.createFadeOutAnimatorProvider(
                 hubContainerView, HubLayoutConstants.FADE_DURATION_MS);
     }
@@ -289,6 +294,16 @@
         return mIsVisibleSupplier.get();
     }
 
+    /** Returns whether the pane is focused. */
+    protected boolean isFocused() {
+        return mPaneHubController != null;
+    }
+
+    /** Returns the PaneHubController if one exists or null otherwise. */
+    protected @Nullable PaneHubController getPaneHubController() {
+        return mPaneHubController;
+    }
+
     /** Returns the current {@link TabSwitcherPaneCoordinator} or null if one doesn't exist. */
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     @Nullable
@@ -308,6 +323,7 @@
                         /* resetHandler= */ this,
                         mIsVisibleSupplier,
                         mIsAnimatingSupplier,
+                        this::onTabClick,
                         mIsIncognito);
         mTabSwitcherPaneCoordinatorSupplier.set(coordinator);
         if (mNativeInitialized) {
@@ -329,6 +345,15 @@
         coordinator.destroy();
     }
 
+    private void onTabClick(int tabId) {
+        if (mPaneHubController == null) return;
+
+        // TODO(crbug/1516949): Consider using INVALID_TAB_ID if already selected to prevent a
+        // repeat selection. For now this is required to ensure the tab gets marked as shown when
+        // exiting the Hub. See if this can be updated/changed.
+        mPaneHubController.selectTabAndHideHub(tabId);
+    }
+
     private void setInitialScrollIndexOffset() {
         TabSwitcherPaneCoordinator coordinator = mTabSwitcherPaneCoordinatorSupplier.get();
         if (coordinator == null) return;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
index 8e394070..04677a3 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinator.java
@@ -87,6 +87,7 @@
      * @param resetHandler The tab list reset handler for the pane.
      * @param isVisibleSupplier The supplier of the pane's visibility.
      * @param isAnimatingSupplier Whether the pane is animating into or out of view.
+     * @param onTabClickCallback Callback to invoke when a tab is clicked.
      * @param mode The {@link TabListMode} to use.
      */
     public TabSwitcherPaneCoordinator(
@@ -107,6 +108,7 @@
             @NonNull TabSwitcherResetHandler resetHandler,
             @NonNull ObservableSupplier<Boolean> isVisibleSupplier,
             @NonNull ObservableSupplier<Boolean> isAnimatingSupplier,
+            @NonNull Callback<Integer> onTabClickCallback,
             @TabListMode int mode) {
         mProfileProviderSupplier = profileProviderSupplier;
         mSnackbarManager = snackbarManager;
@@ -155,7 +157,8 @@
                         parentView,
                         this::onTabSwitcherShown,
                         isVisibleSupplier,
-                        isAnimatingSupplier);
+                        isAnimatingSupplier,
+                        onTabClickCallback);
         mTabSwitcherCustomViewManager = new TabSwitcherCustomViewManager(mMediator);
 
         mMultiThumbnailCardProvider =
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
index 8a35b77..c71ad7e 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactory.java
@@ -11,6 +11,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplier;
@@ -104,6 +105,7 @@
      * @param resetHandler The reset handler to drive updates.
      * @param isVisibleSupplier Supplies visibility information to the tab switcher.
      * @param isAnimatingSupplier Supplies animation information to the tab switcher.
+     * @param onTabClickCallback Callback to be invoked with the tab ID of the selected tab.
      * @param isIncognito Whether this is for the incognito tab switcher.
      * @return a {@link TabSwitcherPaneCoordinator} to use.
      */
@@ -112,6 +114,7 @@
             @NonNull TabSwitcherResetHandler resetHandler,
             @NonNull ObservableSupplier<Boolean> isVisibleSupplier,
             @NonNull ObservableSupplier<Boolean> isAnimatingSupplier,
+            @NonNull Callback<Integer> onTabClickCallback,
             boolean isIncognito) {
         return new TabSwitcherPaneCoordinator(
                 mActivity,
@@ -131,6 +134,7 @@
                 resetHandler,
                 isVisibleSupplier,
                 isAnimatingSupplier,
+                onTabClickCallback,
                 mMode);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
index 54ad2bbe..ae73d86 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorFactoryUnitTest.java
@@ -29,6 +29,7 @@
 import org.mockito.junit.MockitoRule;
 
 import org.chromium.base.BaseSwitches;
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -87,6 +88,7 @@
     @Mock private SnackbarManager mSnackbarManager;
     @Mock private ModalDialogManager mModalDialogManager;
     @Mock private TabSwitcherResetHandler mResetHandler;
+    @Mock private Callback<Integer> mOnTabClickedCallback;
 
     @Captor private ArgumentCaptor<TabModelSelectorObserver> mTabModelSelectorObserverCaptor;
 
@@ -135,6 +137,7 @@
                         mResetHandler,
                         mIsVisibleSupplier,
                         mIsAnimatingSupplier,
+                        mOnTabClickedCallback,
                         /* isIncognito= */ false));
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
index 848b1896..64a40ad 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneCoordinatorUnitTest.java
@@ -101,6 +101,7 @@
     @Mock private SnackbarManager mSnackbarManager;
     @Mock private ModalDialogManager mModalDialogManager;
     @Mock private TabSwitcherResetHandler mResetHandler;
+    @Mock private Callback<Integer> mOnTabClickedCallback;
     @Mock private FaviconHelper.Natives mFaviconHelperJniMock;
     @Mock private Tracker mTracker;
 
@@ -175,6 +176,7 @@
                         mResetHandler,
                         mIsVisibleSupplier,
                         mIsAnimatingSupplier,
+                        mOnTabClickedCallback,
                         TabListMode.GRID);
 
         mCoordinator.initWithNative();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
index 9945962..ca2db57 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediator.java
@@ -100,6 +100,7 @@
     private final ObservableSupplier<Boolean> mIsVisibleSupplier;
     private final ObservableSupplier<Boolean> mIsAnimatingSupplier;
     private final Runnable mOnTabSwitcherShown;
+    private final Callback<Integer> mOnTabClickCallback;
 
     private @Nullable ObservableSupplier<TabListEditorController> mTabListEditorControllerSupplier;
     private @Nullable TransitiveObservableSupplier<TabListEditorController, Boolean>
@@ -117,6 +118,7 @@
      * @param onTabSwitcherShown Runnable executed once the view becomes visible.
      * @param isVisibleSupplier Supplier for visibility of the pane.
      * @param isAnimatingSupplier Supplier for when the pane is animating in or out of visibility.
+     * @param onTabClickCallback Callback to invoke when a tab is clicked.
      */
     public TabSwitcherPaneMediator(
             @NonNull TabSwitcherResetHandler resetHandler,
@@ -126,8 +128,10 @@
             @NonNull ViewGroup containerView,
             @NonNull Runnable onTabSwitcherShown,
             @NonNull ObservableSupplier<Boolean> isVisibleSupplier,
-            @NonNull ObservableSupplier<Boolean> isAnimatingSupplier) {
+            @NonNull ObservableSupplier<Boolean> isAnimatingSupplier,
+            @NonNull Callback<Integer> onTabClickCallback) {
         mResetHandler = resetHandler;
+        mOnTabClickCallback = onTabClickCallback;
         mTabModelFilterSupplier = tabModelFilterSupplier;
         mTabModelFilterSupplier.addObserver(mOnTabModelFilterChanged);
 
@@ -241,7 +245,7 @@
             Tab newlySelectedTab = TabModelUtils.getTabById(model, tabId);
             StartSurfaceUserData.setKeepTab(newlySelectedTab, true);
         }
-        // TODO(crbug/1505772): Forward the selection event to the Hub.
+        mOnTabClickCallback.onResult(tabId);
     }
 
     @Override
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediatorUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediatorUnitTest.java
index c02639c8..5e475f0c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneMediatorUnitTest.java
@@ -38,6 +38,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.shadows.ShadowLooper;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.LazyOneshotSupplier;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
@@ -80,6 +81,7 @@
     @Mock private ViewGroup mContainerView;
     @Mock private View mCustomView;
     @Mock private Runnable mCustomViewBackPressRunnable;
+    @Mock private Callback<Integer> mOnTabClickedCallback;
 
     @Captor private ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
 
@@ -148,7 +150,8 @@
                         mContainerView,
                         mOnTabSwitcherShownRunnable,
                         mIsVisibleSupplier,
-                        mIsAnimatingSupplier);
+                        mIsAnimatingSupplier,
+                        mOnTabClickedCallback);
 
         assertTrue(mTabModelFilterSupplier.hasObservers());
         assertTrue(mIsVisibleSupplier.hasObservers());
@@ -312,12 +315,11 @@
     @Test
     @SmallTest
     public void testOnTabSelecting() {
-        // TODO(crbug/1505772): This test is incomplete since the event should be forwarded to the
-        // Hub.
         assertFalse(StartSurfaceUserData.getKeepTab(mUngroupedTab));
 
         mMediator.onTabSelecting(mUngroupedTab.getId(), /* fromActionButton= */ true);
         assertTrue(StartSurfaceUserData.getKeepTab(mUngroupedTab));
+        verify(mOnTabClickedCallback).onResult(UNGROUPED_TAB_ID);
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneUnitTest.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneUnitTest.java
index 15d5ccb..bb0d1d6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneUnitTest.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherPaneUnitTest.java
@@ -40,6 +40,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.shadows.ShadowLooper;
 
+import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplierImpl;
@@ -49,6 +50,7 @@
 import org.chromium.chrome.browser.hub.HubContainerView;
 import org.chromium.chrome.browser.hub.HubLayoutAnimationType;
 import org.chromium.chrome.browser.hub.LoadHint;
+import org.chromium.chrome.browser.hub.PaneHubController;
 import org.chromium.chrome.browser.hub.PaneId;
 import org.chromium.chrome.browser.price_tracking.PriceTrackingFeatures;
 import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
@@ -77,9 +79,11 @@
     @Mock private View.OnClickListener mNewTabButtonClickListener;
     @Mock private TabModelFilter mTabModelFilter;
     @Mock private MenuOrKeyboardActionController mMenuOrKeyboardActionController;
+    @Mock private PaneHubController mPaneHubController;
 
     @Captor ArgumentCaptor<MenuOrKeyboardActionHandler> mMenuOrKeyboardActionHandlerCaptor;
     @Captor ArgumentCaptor<OnSharedPreferenceChangeListener> mPriceAnnotationsPrefListenerCaptor;
+    @Captor ArgumentCaptor<Callback<Integer>> mOnTabClickedCallbackCaptor;
 
     private final OneshotSupplierImpl<ProfileProvider> mProfileProviderSupplier =
             new OneshotSupplierImpl<>();
@@ -106,7 +110,13 @@
                             return mTabSwitcherPaneCoordinator;
                         })
                 .when(mTabSwitcherPaneCoordinatorFactory)
-                .create(any(), any(), any(), any(), anyBoolean());
+                .create(
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        mOnTabClickedCallbackCaptor.capture(),
+                        anyBoolean());
         when(mTabSwitcherPaneCoordinatorFactory.getTabListMode()).thenReturn(TabListMode.GRID);
         when(mTabSwitcherPaneCoordinator.getHandleBackPressChangedSupplier())
                 .thenReturn(mHandleBackPressChangeSupplier);
@@ -381,19 +391,18 @@
     @SmallTest
     public void testShowTabListEditor() {
         verify(mMenuOrKeyboardActionController, never()).registerMenuOrKeyboardActionHandler(any());
-        mTabSwitcherPane.notifyLoadHint(LoadHint.HOT);
+        mTabSwitcherPane.setPaneHubController(mPaneHubController);
         verify(mMenuOrKeyboardActionController)
                 .registerMenuOrKeyboardActionHandler(mMenuOrKeyboardActionHandlerCaptor.capture());
 
-        MenuOrKeyboardActionHandler handler = mMenuOrKeyboardActionHandlerCaptor.getValue();
         // Check this doesn't crash if there is no coordinator.
-        mTabSwitcherPane.destroyTabSwitcherPaneCoordinator();
+        MenuOrKeyboardActionHandler handler = mMenuOrKeyboardActionHandlerCaptor.getValue();
         assertFalse(
                 handler.handleMenuOrKeyboardAction(
                         org.chromium.chrome.tab_ui.R.id.menu_select_tabs, false));
 
+        mTabSwitcherPane.notifyLoadHint(LoadHint.HOT);
         mTabSwitcherPane.initWithNative();
-        mTabSwitcherPane.createTabSwitcherPaneCoordinator();
         TabSwitcherPaneCoordinator coordinator = mTabSwitcherPane.getTabSwitcherPaneCoordinator();
 
         assertFalse(
@@ -406,11 +415,8 @@
                         org.chromium.chrome.tab_ui.R.id.menu_select_tabs, false));
         verify(coordinator).showTabListEditor();
 
-        mTabSwitcherPane.notifyLoadHint(LoadHint.WARM);
+        mTabSwitcherPane.setPaneHubController(null);
         verify(mMenuOrKeyboardActionController).unregisterMenuOrKeyboardActionHandler(handler);
-        mTabSwitcherPane.notifyLoadHint(LoadHint.COLD);
-        verify(mMenuOrKeyboardActionController, times(2))
-                .unregisterMenuOrKeyboardActionHandler(handler);
     }
 
     @Test
@@ -488,4 +494,20 @@
         mTabSwitcherPane.showAllTabs();
         verify(coordinator).resetWithTabList(mTabModelFilter);
     }
+
+    @Test
+    @SmallTest
+    public void testOnTabClickedCallback() {
+        mTabSwitcherPane.initWithNative();
+        mTabSwitcherPane.createTabSwitcherPaneCoordinator();
+
+        int tabId = 6;
+        mOnTabClickedCallbackCaptor.getValue().onResult(tabId);
+        verify(mPaneHubController, never()).selectTabAndHideHub(tabId);
+
+        mTabSwitcherPane.setPaneHubController(mPaneHubController);
+
+        mOnTabClickedCallbackCaptor.getValue().onResult(tabId);
+        verify(mPaneHubController).selectTabAndHideHub(tabId);
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabContext.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabContext.java
index 17ff0d6..998ceecf 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabContext.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/suggestions/TabContext.java
@@ -8,10 +8,8 @@
 
 import androidx.annotation.Nullable;
 
-import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.components.site_engagement.SiteEngagementService;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -103,11 +101,6 @@
                     tab.isIncognito());
         }
 
-        public double getSiteEngagementScore() {
-            return SiteEngagementService.getForBrowserContext(Profile.getLastUsedRegularProfile())
-                    .getScore(visibleUrl);
-        }
-
         @Override
         public boolean equals(Object other) {
             if (this == other) return true;
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageServiceUnitTest.java
index ed7148d6..42dc4ca 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IncognitoReauthPromoMessageServiceUnitTest.java
@@ -88,7 +88,6 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Profile.setLastUsedProfileForTesting(mProfileMock);
         mJniMocker.mock(UserPrefsJni.TEST_HOOKS, mUserPrefsJniMock);
         when(mUserPrefsJniMock.get(mProfileMock)).thenReturn(mPrefServiceMock);
 
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageServiceUnitTest.java
index 75f6d6c..bb24f65 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/IphMessageServiceUnitTest.java
@@ -43,9 +43,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Profile.setLastUsedProfileForTesting(mProfile);
         TrackerFactory.setTrackerForTests(mTracker);
-        mIphMessageService = new IphMessageService(mIphController);
+        mIphMessageService = new IphMessageService(mProfile, mIphController);
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageServiceUnitTest.java
index 1a584321..eef5e73 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageServiceUnitTest.java
@@ -84,8 +84,6 @@
                 "true");
         FeatureList.setTestValues(testValues);
 
-        Profile.setLastUsedProfileForTesting(mProfile);
-
         PriceTrackingFeatures.setPriceTrackingEnabledForTesting(true);
         PriceTrackingFeatures.setIsSignedInAndSyncEnabledForTesting(true);
         PriceTrackingUtilities.SHARED_PREFERENCES_MANAGER.writeBoolean(
@@ -101,7 +99,7 @@
 
         mMessageService =
                 new PriceMessageService(
-                        mMessageProvider, mReviewActionProvider, mNotificationManager);
+                        mProfile, mMessageProvider, mReviewActionProvider, mNotificationManager);
         mMessageService.addObserver(mMessageObserver);
     }
 
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageServiceUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageServiceUnitTest.java
index 76a6535..269c708 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageServiceUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabSuggestionMessageServiceUnitTest.java
@@ -117,10 +117,11 @@
 
         mMessageService =
                 new TabSuggestionMessageService(
-                        mContext, () -> mTabGroupModelFilter, () -> mTabListEditorController);
+                        mContext,
+                        mProfile,
+                        () -> mTabGroupModelFilter,
+                        () -> mTabListEditorController);
         mMessageService.addObserver(mMessageObserver);
-
-        Profile.setLastUsedProfileForTesting(mProfile);
     }
 
     // Tests for Close suggestions.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
index 541f1004..e339eba 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java
@@ -447,6 +447,7 @@
     @Test
     @SmallTest
     @UiThreadTest
+    @EnableFeatures(ChromeFeatureList.MUlTI_INSTANCE_APPLICATION_STATUS_CLEANUP)
     public void testAllocInstanceId_removeTaskOnRecentScreen_withoutDestroy() {
         assertEquals(0, allocInstanceIndex(PASSED_ID_INVALID, mActivityTask56));
 
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 43bb5944..6e5e3cf2 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -3388,6 +3388,11 @@
                desc="Status text for a download item that is being checked for malware.">
         Checking for malware...
       </message>
+      
+      <!-- Web App Window Title string -->
+      <message name="IDS_WEB_APP_WITH_APP_TITLE" desc="Title of the web app window with app_title.">
+        <ph name="APP_NAME">$1<ex>Sample App</ex></ph> - <ph name="APP_TITLE">$2<ex>News</ex></ph>
+      </message>
 
       <!-- Desktop omnibox PWA install icon -->
       <if expr="not is_android">
@@ -7730,6 +7735,12 @@
       <message name="IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_TITLE" desc="The label for the hue slider in the customization menu on the New Tab Page that allows a user to pick a color for their theme">
         Color picker
       </message>
+      <message name="IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_TITLE" desc="The tooltip for the delete button in the hue slider that unselects the currently selected hue.">
+        Unselect
+      </message>
+      <message name="IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_A11Y_LABEL" desc="The accessibility label for the delete button in the hue slider that unselects the currently selected hue.">
+        Unselect color
+      </message>
       <message name="IDS_NTP_THEME_MANAGED_DIALOG_TITLE" desc="Title text for the dialog informing users that their theme is managed when they try selecting a theme on the New Tab Page.">
         Theme is set by your Organization
       </message>
@@ -7886,9 +7897,6 @@
       <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_HEADER" desc="Header for the wallpaper search section for showing past themes." >
         Your recent AI themes
       </message>
-      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE" desc="Title for each tile button in the wallpaper search section for showing past themes.">
-        Recent AI theme <ph name="TILE_INDEX">$1<ex>2</ex></ph>
-      </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_OFFLINE_DESCRIPTION" desc="The description of the message displayed by wallpaper search when the browser is offline.">
         Check your internet and try again.
       </message>
@@ -7928,17 +7936,32 @@
       <message name="IDS_NTP_WALLPAPER_SEARCH_TRY_AGAIN_CTA" desc="Call to action displayed when wallpaper search fails that asks user to try again.">
         Try again
       </message>
+      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_NO_DESCRIPTOR" desc="Accessibility label for a wallpaper search history result, with only the index.">
+        Recent AI theme <ph name="INDEX">$1<ex>2</ex></ph>
+      </message>
+      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL" desc="Accessibility label for a wallpaper search history result, with only descriptor A.">
+        Recent AI theme <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>
+      </message>
+      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_B" desc="Accessibility label for a wallpaper search history result, with descriptors A and B.">
+        Recent AI theme <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, in <ph name="STYLE">$3</ph> style
+      </message>
+      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_C" desc="Accessibility label for a wallpaper search history result, with descriptors A and C.">
+        Recent AI theme <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, with <ph name="MOOD">$3</ph> mood.
+      </message>
+      <message name="IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C" desc="Accessibility label for a wallpaper search history result, with descriptors A, B, and C.">
+        Recent AI theme <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, in <ph name="STYLE">$3</ph> style, with <ph name="MOOD">$4</ph> mood.
+      </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL" desc="Accessibility label for a wallpaper search result, with only descriptor A.">
-        Image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="A">$2</ph>
+        Generated image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>
       </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_B" desc="Accessibility label for a wallpaper search result, with descriptors A and B.">
-        Image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="A">$2</ph>, in <ph name="B">$3</ph> style
+        Generated image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, in <ph name="STYLE">$3</ph> style
       </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_C" desc="Accessibility label for a wallpaper search result, with descriptors A and C.">
-        Image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="A">$2</ph>, with <ph name="C">$3</ph> mood.
+        Generated image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, with <ph name="MOOD">$3</ph> mood.
       </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C" desc="Accessibility label for a wallpaper search result, with descriptors A, B, and C.">
-        Image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="A">$2</ph>, in <ph name="B">$3</ph> style, with <ph name="C">$4</ph> mood.
+        Generated image <ph name="INDEX">$1<ex>2</ex></ph> of <ph name="SUBJECT">$2</ph>, in <ph name="STYLE">$3</ph> style, with <ph name="MOOD">$4</ph> mood.
       </message>
       <message name="IDS_NTP_WALLPAPER_SEARCH_SUBJECT_LABEL" desc="Label for the Create theme with AI dropdown for selecting subject of the image.">
         Subject
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_A11Y_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_A11Y_LABEL.png.sha1
new file mode 100644
index 0000000..af1edd4
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_A11Y_LABEL.png.sha1
@@ -0,0 +1 @@
+728d7b9f4f2d9cbc3604f097b29e7a3d2642d65b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_TITLE.png.sha1
new file mode 100644
index 0000000..7cdba5db
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_TITLE.png.sha1
@@ -0,0 +1 @@
+dd934c7a0a18af3d4d47e662c6d0b802c7bf6c60
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL.png.sha1
similarity index 100%
rename from chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1
rename to chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_NO_DESCRIPTOR.png.sha1
similarity index 100%
copy from chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1
copy to chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_NO_DESCRIPTOR.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1
similarity index 100%
copy from chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1
copy to chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1
similarity index 100%
copy from chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1
copy to chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1
similarity index 100%
copy from chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE.png.sha1
copy to chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL.png.sha1
index 85d05eb2..5dd8fa6 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL.png.sha1
@@ -1 +1 @@
-3c1aa0835be426969bb0502004773c1a40bf169b
\ No newline at end of file
+5f6cd954e8ae25200a891e2f72b79a12086004ab
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1
index 85d05eb2..5dd8fa6 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C.png.sha1
@@ -1 +1 @@
-3c1aa0835be426969bb0502004773c1a40bf169b
\ No newline at end of file
+5f6cd954e8ae25200a891e2f72b79a12086004ab
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1
index 85d05eb2..5dd8fa6 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_B.png.sha1
@@ -1 +1 @@
-3c1aa0835be426969bb0502004773c1a40bf169b
\ No newline at end of file
+5f6cd954e8ae25200a891e2f72b79a12086004ab
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1 b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1
index 85d05eb2..5dd8fa6 100644
--- a/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1
+++ b/chrome/app/generated_resources_grd/IDS_NTP_WALLPAPER_SEARCH_RESULT_LABEL_WITH_DESCRIPTOR_C.png.sha1
@@ -1 +1 @@
-3c1aa0835be426969bb0502004773c1a40bf169b
\ No newline at end of file
+5f6cd954e8ae25200a891e2f72b79a12086004ab
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_WEB_APP_WITH_APP_TITLE.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEB_APP_WITH_APP_TITLE.png.sha1
new file mode 100644
index 0000000..204347f
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEB_APP_WITH_APP_TITLE.png.sha1
@@ -0,0 +1 @@
+6b9f169c1bca991eaa7722ab2c6ee729543ef0d8
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index dd20367..280b14b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -8127,6 +8127,12 @@
      FEATURE_VALUE_TYPE(
          content_settings::features::kImprovedSemanticsActivityIndicators)},
 
+    {"left-hand-side-activity-indicators",
+     flag_descriptions::kLeftHandSideActivityIndicatorsName,
+     flag_descriptions::kLeftHandSideActivityIndicatorsDescription, kOsDesktop,
+     FEATURE_VALUE_TYPE(
+         content_settings::features::kLeftHandSideActivityIndicators)},
+
     {"attribution-reporting-debug-mode",
      flag_descriptions::kAttributionReportingDebugModeName,
      flag_descriptions::kAttributionReportingDebugModeDescription, kOsAll,
diff --git a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
index 8578c25d..06f7e93d 100644
--- a/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
+++ b/chrome/browser/ash/app_list/search/essential_search/essential_search_manager.cc
@@ -22,7 +22,9 @@
 EssentialSearchManager::EssentialSearchManager(Profile* primary_profile)
     : primary_profile_(primary_profile) {
   DCHECK(primary_profile_);
-  scoped_observation_.Observe(ash::SessionController::Get());
+  auto* session_controller = ash::SessionController::Get();
+  CHECK(session_controller);
+  scoped_observation_.Observe(session_controller);
 }
 
 EssentialSearchManager::~EssentialSearchManager() = default;
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc b/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
index 5ca0488..7fe2beb 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_edit_view.cc
@@ -60,8 +60,8 @@
       views::CreateEmptyBorder(gfx::Insets::VH(14, kHorizontalInsets)));
   container->SetBackground(views::CreateThemedRoundedRectBackground(
       cros_tokens::kCrosSysSystemOnBase,
-      /*top_radius=*/kCornerRadius,
-      /*bottom_radius=*/for_editing_list ? kCornerRadius : 0.0f,
+      /*top_radius=*/for_editing_list ? kCornerRadius : 0.0f,
+      /*bottom_radius=*/kCornerRadius,
       /*for_border_thickness=*/0));
   const int padding_width = for_editing_list
                                 ? kNameTagAndLabelsPaddingForEditingList
@@ -94,11 +94,12 @@
 
   // Set highlight path.
   views::HighlightPathGenerator::Install(
-      this, std::make_unique<views::RoundRectHighlightPathGenerator>(
-                gfx::Insets(), for_editing_list
-                                   ? gfx::RoundedCornersF(kCornerRadius)
-                                   : gfx::RoundedCornersF(
-                                         kCornerRadius, kCornerRadius, 0, 0)));
+      this,
+      std::make_unique<views::RoundRectHighlightPathGenerator>(
+          gfx::Insets(), for_editing_list
+                             ? gfx::RoundedCornersF(kCornerRadius)
+                             : gfx::RoundedCornersF(0.0f, 0.0f, kCornerRadius,
+                                                    kCornerRadius)));
 }
 
 ActionEditView::~ActionEditView() = default;
diff --git a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
index 455e0b3..3d39f59 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
@@ -185,8 +185,8 @@
       views::BoxLayout::Orientation::kVertical));
   AddHeader();
   AddEditTitle();
-  AddActionEdit();
   AddActionSelection();
+  AddActionEdit();
   AddDeleteButton();
 }
 
@@ -245,16 +245,6 @@
                      gfx::Insets::TLBR(0, kHeaderLeftMarginSpacing, 16, 0));
 }
 
-void ButtonOptionsMenu::AddActionEdit() {
-  // ------------------------------
-  // ||"Selected key" |key labels||
-  // ||"key"                      |
-  // ------------------------------
-  action_edit_ = AddChildView(
-      std::make_unique<ButtonOptionsActionEdit>(controller_, action_));
-  action_name_label_->SetText(action_edit_->GetActionName());
-}
-
 void ButtonOptionsMenu::AddActionSelection() {
   // ----------------------------------
   // | |"Choose your button type:"  | |
@@ -262,10 +252,10 @@
   // ----------------------------------
   auto* container = AddChildView(std::make_unique<views::View>());
   container->SetBackground(views::CreateThemedRoundedRectBackground(
-      cros_tokens::kCrosSysSystemOnBase, /*top_radius=*/0,
-      /*bottom_radius=*/16, /*for_border_thickness=*/0));
+      cros_tokens::kCrosSysSystemOnBase, /*top_radius=*/16.0f,
+      /*bottom_radius=*/0.0f, /*for_border_thickness=*/0.0f));
   container->SetUseDefaultFillLayout(true);
-  container->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(2, 0, 0, 0));
+  container->SetProperty(views::kMarginsKey, gfx::Insets::TLBR(0, 0, 2, 0));
   container->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
       /*inside_border_insets=*/gfx::Insets::TLBR(12, 16, 16, 16),
@@ -284,6 +274,16 @@
       ActionTypeButtonGroup::CreateButtonGroup(controller_, action_));
 }
 
+void ButtonOptionsMenu::AddActionEdit() {
+  // ------------------------------
+  // ||"Selected key" |key labels||
+  // ||"key"                      |
+  // ------------------------------
+  action_edit_ = AddChildView(
+      std::make_unique<ButtonOptionsActionEdit>(controller_, action_));
+  action_name_label_->SetText(action_edit_->GetActionName());
+}
+
 void ButtonOptionsMenu::AddDeleteButton() {
   // ------------------------------
   // ||      Delete button       ||
diff --git a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h
index d693c85..d9524d0 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h
@@ -37,13 +37,13 @@
 // |----------------------------------|
 // ||"Buttons let..."|                |
 // |----------------------------------|
-// ||"Selected key"       |key labels||
-// ||"key"                            |
-// |----------------------------------|
 // |  |"Choose your button type:"   | |
 // |  |feature_tile|  |feature_title| |
 // |  |            |  |             | |
 // |----------------------------------|
+// ||"Selected key"       |key labels||
+// ||"key"                            |
+// |----------------------------------|
 // -----------------------------------|
 // ||         Delete button          ||
 // +----------------------------------+
@@ -69,8 +69,8 @@
   // Add UI components.
   void AddHeader();
   void AddEditTitle();
-  void AddActionEdit();
   void AddActionSelection();
+  void AddActionEdit();
   void AddDeleteButton();
 
   // Functions related to buttons.
diff --git a/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.cc b/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.cc
index a0bf635..56cf8ec 100644
--- a/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.cc
+++ b/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.cc
@@ -39,8 +39,6 @@
 using BufferEvents = ArcTracingGraphicsModel::BufferEvents;
 using EventType = ArcTracingGraphicsModel::EventType;
 
-constexpr char kCustomTracePrefix[] = "customTrace";
-
 constexpr char kUnknownActivity[] = "unknown";
 
 constexpr char kKeyActivity[] = "activity";
@@ -200,43 +198,19 @@
   route->pop_back();
 }
 
-void ScanForCustomEvents(
-    const ArcTracingEvent* event,
-    ArcTracingGraphicsModel::BufferEvents* out_custom_events) {
-  if (base::StartsWith(event->GetName(), kCustomTracePrefix,
-                       base::CompareCase::SENSITIVE)) {
-    DCHECK(!event->GetArgs() || event->GetArgs()->empty());
-    out_custom_events->emplace_back(
-        ArcTracingGraphicsModel::EventType::kCustomEvent, event->GetTimestamp(),
-        event->GetName().substr(std::size(kCustomTracePrefix) - 1));
-  }
-  for (const auto& child : event->children())
-    ScanForCustomEvents(child.get(), out_custom_events);
-}
-
-// Extracts custom events from the model. Custom events start from customTrace
-ArcTracingGraphicsModel::BufferEvents GetCustomEvents(
-    const ArcTracingModel& common_model) {
-  ArcTracingGraphicsModel::BufferEvents custom_events;
-  for (const ArcTracingEvent* root : common_model.GetRoots())
-    ScanForCustomEvents(root, &custom_events);
-  return custom_events;
-}
-
 // Adds jank events into |ArcTracingGraphicsModel::EventsContainer|.
 // |pulse_event_type| defines the type of the event that should appear
 // periodically. Once it is missed in analyzed buffer events, new jank event is
 // added. |jank_event_type| defines the type of jank.
-void AddJanks(ArcTracingGraphicsModel::EventsContainer* result,
+void AddJanks(std::vector<BufferEvent>* events,
               EventType pulse_event_type,
               EventType jank_event_type) {
   // Detect rate first.
   BufferEvents pulse_events;
 
-  for (const auto& it : result->buffer_events()) {
-    for (const auto& it_event : it) {
-      if (it_event.type == pulse_event_type)
-        pulse_events.emplace_back(it_event);
+  for (const auto& ev : *events) {
+    if (ev.type == pulse_event_type) {
+      pulse_events.emplace_back(ev);
     }
   }
   SortBufferEventsByTimestamp(&pulse_events);
@@ -248,7 +222,7 @@
             BufferEvent(jank_event_type,
                         timestamp.ToDeltaSinceWindowsEpoch().InMicroseconds()));
       },
-      jank_event_type, &result->global_events()));
+      jank_event_type, events));
 
   for (const auto& it : pulse_events) {
     jank_detector.OnSample(base::Time::FromDeltaSinceWindowsEpoch(
@@ -266,6 +240,8 @@
     jank_detector.OnSample(base::Time::FromDeltaSinceWindowsEpoch(
         base::Microseconds(it.timestamp)));
   }
+
+  SortBufferEventsByTimestamp(events);
 }
 
 // Helper that performs query in |common_model| for top level Chrome GPU events
@@ -280,8 +256,6 @@
   }
 
   SortBufferEventsByTimestamp(&result->buffer_events()[0]);
-
-  // TODO(matvore): Record Janks in the ChromeOS swap done pulse.
 }
 
 // Helper that serializes events |events| to the |base::Value::List|.
@@ -341,12 +315,13 @@
     if (!IsInRange(type, EventType::kBufferQueueDequeueStart,
                    EventType::kBufferFillJank) &&
         !IsInRange(type, EventType::kExoSurfaceAttach,
-                   EventType::kExoSurfaceCommit) &&
+                   EventType::kExoLastEvent) &&
         !IsInRange(type, EventType::kChromeBarrierOrder,
                    EventType::kChromeBarrierFlush) &&
         !IsInRange(type, EventType::kSurfaceFlingerVsyncHandler,
                    EventType::kVsyncTimestamp) &&
-        !IsInRange(type, EventType::kChromeOSDraw, EventType::kChromeOSJank) &&
+        !IsInRange(type, EventType::kChromeOSDraw,
+                   EventType::kChromeOSLastEvent) &&
         !IsInRange(type, EventType::kCustomEvent, EventType::kCustomEvent) &&
         !IsInRange(type, EventType::kInputEventCreated,
                    EventType::kInputEventDeliverEnd)) {
@@ -463,24 +438,16 @@
   for (int64_t ticks : present_frames.commits()) {
     buffer_events[0].emplace_back(EventType::kExoSurfaceCommit, ticks);
   }
+  AddJanks(&buffer_events[0], EventType::kExoSurfaceCommit,
+           EventType::kExoSurfaceCommitJank);
+
   for (int64_t ticks : present_frames.presents()) {
     chrome_top_level_.global_events().emplace_back(
         EventType::kChromeOSPresentationDone, ticks);
   }
-
-  // TODO(khmel): Add more information to resolve owner of custom events. At
-  // this moment add custom events to each view.
-  const ArcTracingGraphicsModel::BufferEvents custom_events =
-      GetCustomEvents(common_model);
-  for (auto& it : view_buffers_) {
-    AddJanks(&it.second, EventType::kBufferQueueDequeueStart,
-             EventType::kBufferFillJank);
-    AddJanks(&it.second, EventType::kExoSurfaceCommit, EventType::kExoJank);
-    it.second.global_events().insert(it.second.global_events().end(),
-                                     custom_events.begin(),
-                                     custom_events.end());
-    SortBufferEventsByTimestamp(&it.second.global_events());
-  }
+  AddJanks(&chrome_top_level_.global_events(),
+           EventType::kChromeOSPresentationDone,
+           EventType::kChromeOSPerceivedJank);
 
   GetChromeTopLevelEvents(common_model, &chrome_top_level_);
   if (chrome_top_level_.buffer_events().empty()) {
@@ -488,6 +455,8 @@
     if (!skip_structure_validation_)
       return false;
   }
+  AddJanks(&chrome_top_level_.buffer_events()[0], EventType::kChromeOSSwapDone,
+           EventType::kChromeOSSwapJank);
 
   system_model_.CopyFrom(common_model.system_model());
 
diff --git a/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h b/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h
index b3a1f2b..66286d6 100644
--- a/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h
+++ b/chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h
@@ -56,13 +56,15 @@
     kBufferFillJank,                 // 106,
 
     // Wayland exo events
-    kExoSurfaceAttach   = 200,  // Obsolete
-    kExoProduceResource = 201,  // Obsolete
-    kExoBound           = 202,  // Obsolete
-    kExoPendingQuery    = 203,  // Obsolete
-    kExoReleased        = 204,  // Obsolete
-    kExoJank            = 205,
-    kExoSurfaceCommit   = 206,
+    kExoSurfaceAttach     = 200,  // Obsolete
+    kExoProduceResource   = 201,  // Obsolete
+    kExoBound             = 202,  // Obsolete
+    kExoPendingQuery      = 203,  // Obsolete
+    kExoReleased          = 204,  // Obsolete
+    kExoJank              = 205,
+    kExoSurfaceCommit     = 206,
+    kExoSurfaceCommitJank = 207,
+    kExoLastEvent         = kExoSurfaceCommitJank,
 
     // Chrome events
     kChromeBarrierOrder = 300,  // Obsolete
@@ -84,9 +86,12 @@
     kChromeOSPresentationDone = 503,
     kChromeOSSwapDone         = 504,
     kChromeOSJank             = 505,  // Obsolete
+    kChromeOSPerceivedJank    = 506,
+    kChromeOSSwapJank         = 507,
+    kChromeOSLastEvent        = kChromeOSSwapJank,
 
     // Custom event.
-    kCustomEvent = 600,
+    kCustomEvent = 600,  // Obsolete
 
     // Input events
     kInputEventCreated           = 700,  // Obsolete
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
index cf5db34..b9f5e93 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
@@ -50,6 +50,7 @@
 #include "chrome/browser/ash/accessibility/accessibility_event_rewriter_delegate_impl.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/ash/accessibility/magnification_manager.h"
+#include "chrome/browser/ash/app_list/search/essential_search/essential_search_manager.h"
 #include "chrome/browser/ash/app_mode/app_launch_utils.h"
 #include "chrome/browser/ash/app_mode/arc/arc_kiosk_app_manager.h"
 #include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h"
@@ -1258,6 +1259,9 @@
 
     misconfigured_user_cleaner_->ScheduleCleanup();
 
+    essential_search_manager_ =
+        ::app_list::EssentialSearchManager::Create(profile);
+
     g_browser_process->platform_part()->session_manager()->Initialize(
         *base::CommandLine::ForCurrentProcess(), profile,
         is_integration_test());
@@ -1565,6 +1569,7 @@
   login_screen_extensions_storage_cleaner_.reset();
   debugd_notification_handler_.reset();
   shortcut_mapping_pref_service_.reset();
+  essential_search_manager_.reset();
   if (features::IsTrafficCountersEnabled()) {
     traffic_counters_handler_.reset();
   }
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.h b/chrome/browser/ash/chrome_browser_main_parts_ash.h
index 0c7dcb0..1b0be40 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.h
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.h
@@ -23,6 +23,10 @@
 class ChromeKeyboardControllerClient;
 class ImageDownloaderImpl;
 
+namespace app_list {
+class EssentialSearchManager;
+}
+
 namespace arc {
 class ArcServiceLauncher;
 class ContainerAppKiller;
@@ -309,6 +313,8 @@
 
   std::unique_ptr<MisconfiguredUserCleaner> misconfigured_user_cleaner_;
 
+  std::unique_ptr<::app_list::EssentialSearchManager> essential_search_manager_;
+
   base::WeakPtrFactory<ChromeBrowserMainPartsAsh> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/ash/crosapi/crosapi_util.cc b/chrome/browser/ash/crosapi/crosapi_util.cc
index 66e73c8..111668f 100644
--- a/chrome/browser/ash/crosapi/crosapi_util.cc
+++ b/chrome/browser/ash/crosapi/crosapi_util.cc
@@ -205,6 +205,10 @@
 // Bug fix to launch tabbed web app windows in new windows when requested.
 // We can remove this capability once Ash and Lacros are both past M122.
 constexpr char kAshShelfNewWindowFix[] = "crbug/1490336";
+// Support feedback dialog ai flow.
+// TODO(crbug/1501057): Remove this capability once Ash and Lacros are both past
+// M123.
+constexpr char kAshFeedbackFlowAi[] = "crbug/1501057";
 
 // Returns the vector containing policy data of the device account. In case of
 // an error, returns nullopt.
@@ -630,6 +634,7 @@
       kAshExtensionKeeplistCmdlineSwitchCapability,
       kAshAppInstallServicePackageIdFix,
       kAshShelfNewWindowFix,
+      kAshFeedbackFlowAi,
   };
   params->ash_capabilities = {std::move(ash_capabilities)};
 
diff --git a/chrome/browser/ash/crosapi/feedback_ash.cc b/chrome/browser/ash/crosapi/feedback_ash.cc
index f5c74c0..e1fc0495 100644
--- a/chrome/browser/ash/crosapi/feedback_ash.cc
+++ b/chrome/browser/ash/crosapi/feedback_ash.cc
@@ -36,6 +36,8 @@
       return chrome::kFeedbackSourceProfileErrorDialog;
     case mojom::LacrosFeedbackSource::kFeedbackSourceQuickOffice:
       return chrome::kFeedbackSourceQuickOffice;
+    case mojom::LacrosFeedbackSource::kFeedbackSourceAI:
+      return chrome::kFeedbackSourceAI;
     case mojom::LacrosFeedbackSource::kUnknown:
       return chrome::kFeedbackSourceUnknownLacrosSource;
   }
@@ -67,14 +69,26 @@
   }
   base::Value::Dict autofill_metadata;
   if (feedback_info->autofill_metadata) {
-    DCHECK(feedback_info->autofill_metadata->is_dict());
+    if (!feedback_info->autofill_metadata->is_dict()) {
+      LOG(ERROR) << "Feedback info autofill metadata is not a dict.";
+      return;
+    }
     autofill_metadata = std::move(*feedback_info->autofill_metadata).TakeDict();
   }
+  base::Value::Dict ai_metadata;
+  if (feedback_info->ai_metadata) {
+    if (!feedback_info->ai_metadata->is_dict()) {
+      LOG(ERROR) << "Feedback info ai metadata is not a dict.";
+      return;
+    }
+    ai_metadata = std::move(*feedback_info->ai_metadata).TakeDict();
+  }
   chrome::ShowFeedbackPage(
       feedback_info->page_url, profile, FromMojo(feedback_info->source),
       feedback_info->description_template,
       feedback_info->description_placeholder_text, feedback_info->category_tag,
-      feedback_info->extra_diagnostics, std::move(autofill_metadata));
+      feedback_info->extra_diagnostics, std::move(autofill_metadata),
+      std::move(ai_metadata));
 }
 
 }  // namespace crosapi
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 66480cc..c721212 100644
--- a/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc
+++ b/chrome/browser/ash/extensions/file_manager/private_api_tasks.cc
@@ -32,7 +32,6 @@
 #include "extensions/browser/entry_info.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "storage/browser/file_system/file_system_url.h"
-#include "ui/gfx/native_widget_types.h"
 
 namespace extensions {
 namespace {
@@ -134,14 +133,8 @@
     urls.push_back(url);
   }
 
-  // Get Files App window, if it exists.
-  Browser* browser =
-      FindSystemWebAppBrowser(profile, ash::SystemWebAppType::FILE_MANAGER);
-  gfx::NativeWindow modal_parent =
-      browser ? browser->window()->GetNativeWindow() : nullptr;
-
   const bool result = file_manager::file_tasks::ExecuteFileTask(
-      profile, task, urls, modal_parent,
+      profile, task, urls,
       base::BindOnce(
           &FileManagerPrivateInternalExecuteTaskFunction::OnTaskExecuted,
           this));
diff --git a/chrome/browser/ash/file_manager/file_tasks.cc b/chrome/browser/ash/file_manager/file_tasks.cc
index 6d6fd943..125097c 100644
--- a/chrome/browser/ash/file_manager/file_tasks.cc
+++ b/chrome/browser/ash/file_manager/file_tasks.cc
@@ -782,7 +782,6 @@
 bool ExecuteFileTask(Profile* profile,
                      const TaskDescriptor& task,
                      const std::vector<FileSystemURL>& file_urls,
-                     gfx::NativeWindow modal_parent,
                      FileTaskFinishedCallback done) {
   // Save some of the arguments of "the most recent ExecuteFileTask" in JSON
   // (base::Value) format.
@@ -818,7 +817,7 @@
       RecordOfficeOpenExtensionDriveMetric(file_url);
     }
     const bool started = ExecuteWebDriveOfficeTask(
-        profile, task, file_urls, modal_parent,
+        profile, task, file_urls,
         std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
             ash::cloud_upload::CloudProvider::kGoogleDrive, file_urls.size()));
     if (done) {
@@ -839,7 +838,7 @@
       RecordOfficeOpenExtensionOneDriveMetric(file_url);
     }
     const bool started = ExecuteOpenInOfficeTask(
-        profile, task, file_urls, modal_parent,
+        profile, task, file_urls,
         std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
             ash::cloud_upload::CloudProvider::kOneDrive, file_urls.size()));
     if (done) {
@@ -859,8 +858,7 @@
   }
   // TODO(b/284800493): Add a test that VirtualTasks get run.
   if (IsVirtualTask(task)) {
-    const bool started =
-        ExecuteVirtualTask(profile, task, file_urls, modal_parent);
+    const bool started = ExecuteVirtualTask(profile, task, file_urls);
     if (done) {
       if (started) {
         std::move(done).Run(
diff --git a/chrome/browser/ash/file_manager/file_tasks.h b/chrome/browser/ash/file_manager/file_tasks.h
index cdcf964..ee709e0 100644
--- a/chrome/browser/ash/file_manager/file_tasks.h
+++ b/chrome/browser/ash/file_manager/file_tasks.h
@@ -105,7 +105,6 @@
 #include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
-#include "ui/gfx/native_widget_types.h"
 #include "url/gurl.h"
 
 class PrefService;
@@ -298,15 +297,12 @@
 // profile      - The profile used for making this function call.
 // task         - See the comment at TaskDescriptor struct.
 // file_urls    - URLs of the target files.
-// modal_parent - Certain tasks like the Office setup flow can create WebUIs,
-//                which will be made modal to this parent, if not null.
 // done         - The callback which will be called on completion.
 //                The callback won't be called if the function returns
 //                false.
 bool ExecuteFileTask(Profile* profile,
                      const TaskDescriptor& task,
                      const std::vector<storage::FileSystemURL>& file_urls,
-                     gfx::NativeWindow modal_parent,
                      FileTaskFinishedCallback done);
 
 // See ash::FilesInternalsDebugJSONProvider::FunctionPointerType in
diff --git a/chrome/browser/ash/file_manager/file_tasks_browsertest.cc b/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
index 31f233be..ee80df6 100644
--- a/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_tasks_browsertest.cc
@@ -306,7 +306,7 @@
     base::test::TestFuture<std::string> message;
     DomMessageObserverAsh observer(&message);
     lacros_waiter_->ObserveDomMessages(observer.Bind());
-    ExecuteFileTask(profile(), task, files, nullptr, base::DoNothing());
+    ExecuteFileTask(profile(), task, files, base::DoNothing());
     return message.Get();
   }
 
@@ -344,7 +344,7 @@
       const TaskDescriptor& task,
       const std::vector<storage::FileSystemURL>& files) override {
     content::DOMMessageQueue message_queue;
-    ExecuteFileTask(profile(), task, files, nullptr, base::DoNothing());
+    ExecuteFileTask(profile(), task, files, base::DoNothing());
     std::string message;
     CHECK(message_queue.WaitForMessage(&message));
     return message;
@@ -732,7 +732,7 @@
   std::vector<storage::FileSystemURL> files;
   files.push_back(storage::FileSystemURL::CreateForTest(url1));
   files.push_back(storage::FileSystemURL::CreateForTest(url2));
-  ExecuteFileTask(profile, task_descriptor, files, nullptr, base::DoNothing());
+  ExecuteFileTask(profile, task_descriptor, files, base::DoNothing());
   run_loop.Run();
 }
 
@@ -798,7 +798,7 @@
   // GetUserFallbackChoice() returns `True` because the Fallback dialog can be
   // shown.
   ASSERT_TRUE(GetUserFallbackChoice(
-      profile, CreateWebDriveOfficeTask(), {test_url}, nullptr,
+      profile, CreateWebDriveOfficeTask(), {test_url},
       ash::office_fallback::FallbackReason::kOffline,
       std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
           ash::cloud_upload::CloudProvider::kOneDrive, /*file_count=*/1)));
@@ -820,7 +820,7 @@
   // installed.
   storage::FileSystemURL test_url;
   ASSERT_FALSE(GetUserFallbackChoice(
-      browser()->profile(), CreateWebDriveOfficeTask(), {test_url}, nullptr,
+      browser()->profile(), CreateWebDriveOfficeTask(), {test_url},
       ash::office_fallback::FallbackReason::kOffline,
       std::make_unique<ash::cloud_upload::CloudOpenMetrics>(
           ash::cloud_upload::CloudProvider::kOneDrive, /*file_count=*/1)));
@@ -925,8 +925,8 @@
         /*should_launch_browser=*/true, account_id);
   }
 
-  // Launch Files app and return its NativeWindow.
-  gfx::NativeWindow LaunchFilesAppAndWait() {
+  // Launch Files app and wait for it to open.
+  void LaunchFilesAppAndWait() {
     GURL files_swa_url = util::GetFileManagerMainPageUrlWithParams(
         ui::SelectFileDialog::SELECT_NONE, /*title=*/std::u16string(),
         /*current_directory_url=*/{},
@@ -940,8 +940,7 @@
     params.url = files_swa_url;
     ash::LaunchSystemWebAppAsync(browser()->profile(),
                                  ash::SystemWebAppType::FILE_MANAGER, params);
-    Browser* files_app = ui_test_utils::WaitForBrowserToOpen();
-    return files_app->window()->GetNativeWindow();
+    ui_test_utils::WaitForBrowserToOpen();
   }
 
  protected:
@@ -1354,7 +1353,7 @@
 
   // Launches the office fallback dialog as the system is offline.
   base::test::TestFuture<TaskResult, std::string> executed_future;
-  ExecuteFileTask(profile(), web_drive_office_task, file_urls, nullptr,
+  ExecuteFileTask(profile(), web_drive_office_task, file_urls,
                   executed_future.GetCallback());
   ASSERT_EQ(executed_future.Get<0>(), TaskResult::kOpened);
 
@@ -1376,7 +1375,7 @@
   // because system is online.
   OnDialogChoiceReceived(profile(), web_drive_office_task, file_urls,
                          ash::office_fallback::FallbackReason::kOffline,
-                         nullptr, std::move(cloud_open_metrics_),
+                         std::move(cloud_open_metrics_),
                          ash::office_fallback::kDialogChoiceTryAgain);
 
   // Wait for file to open in web drive office.
@@ -1420,7 +1419,7 @@
 
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), file_urls, ash::cloud_upload::CloudProvider::kGoogleDrive,
-      nullptr, std::move(cloud_open_metrics_)));
+      std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
   // Wait for file to open in web drive office.
@@ -1460,10 +1459,10 @@
       expected_dialog_URL);
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait();
+  LaunchFilesAppAndWait();
 
   // Triggers setup flow.
-  ExecuteFileTask(profile(), web_drive_office_task, file_urls, modal_parent,
+  ExecuteFileTask(profile(), web_drive_office_task, file_urls,
                   base::DoNothing());
 
   // Wait for setup flow dialog to open.
@@ -1497,10 +1496,10 @@
       expected_dialog_URL);
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait();
+  LaunchFilesAppAndWait();
 
   // Triggers setup flow.
-  ExecuteFileTask(profile(), web_drive_office_task, file_urls, modal_parent,
+  ExecuteFileTask(profile(), web_drive_office_task, file_urls,
                   base::DoNothing());
 
   // Wait for setup flow dialog to open.
@@ -1738,7 +1737,7 @@
 
   // Launches the office fallback dialog as the system is offline.
   base::test::TestFuture<TaskResult, std::string> executed_future;
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, nullptr,
+  ExecuteFileTask(profile(), open_in_office_task, file_urls,
                   executed_future.GetCallback());
   ASSERT_EQ(executed_future.Get<0>(), TaskResult::kOpened);
 
@@ -1754,7 +1753,7 @@
   // because system is online, and the file doesn't need to be moved.
   OnDialogChoiceReceived(profile(), open_in_office_task, file_urls,
                          ash::office_fallback::FallbackReason::kOffline,
-                         nullptr, std::move(cloud_open_metrics_),
+                         std::move(cloud_open_metrics_),
                          ash::office_fallback::kDialogChoiceTryAgain);
 
   auto launches = web_app_publisher_->GetLaunches();
@@ -1803,7 +1802,7 @@
 
   // Launches the office fallback dialog as the system is offline.
   base::test::TestFuture<TaskResult, std::string> executed_future;
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, nullptr,
+  ExecuteFileTask(profile(), open_in_office_task, file_urls,
                   executed_future.GetCallback());
   ASSERT_EQ(executed_future.Get<0>(), TaskResult::kOpened);
 
@@ -1819,7 +1818,7 @@
   // open.
   OnDialogChoiceReceived(profile(), open_in_office_task, file_urls,
                          ash::office_fallback::FallbackReason::kOffline,
-                         nullptr, std::move(cloud_open_metrics_),
+                         std::move(cloud_open_metrics_),
                          ash::office_fallback::kDialogChoiceCancel);
 
   ASSERT_EQ(0u, web_app_publisher_->GetLaunches().size());
@@ -1859,7 +1858,7 @@
   // Launches the first office fallback dialog as the system is offline. Let it
   // hang waiting for a choice from the user.
   base::test::TestFuture<TaskResult, std::string> executed_future;
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, nullptr,
+  ExecuteFileTask(profile(), open_in_office_task, file_urls,
                   executed_future.GetCallback());
   ASSERT_EQ(executed_future.Get<0>(), TaskResult::kOpened);
 
@@ -1869,7 +1868,7 @@
 
   // Fails to launch a second office fallback dialog.
   base::test::TestFuture<TaskResult, std::string> failed_future;
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, nullptr,
+  ExecuteFileTask(profile(), open_in_office_task, file_urls,
                   failed_future.GetCallback());
   ASSERT_EQ(failed_future.Get<0>(), TaskResult::kFailed);
 
@@ -1901,7 +1900,7 @@
   // Open file directly from ODFS.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive,
-      nullptr, std::move(cloud_open_metrics_)));
+      std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
   auto launches = web_app_publisher_->GetLaunches();
@@ -1936,12 +1935,12 @@
       expected_dialog_URL);
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait();
+  LaunchFilesAppAndWait();
 
   // Triggers Move Confirmation dialog.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive,
-      modal_parent, std::move(cloud_open_metrics_)));
+      std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
   // Wait for Move Confirmation dialog to open.
@@ -1976,7 +1975,7 @@
   // Open file directly from ODFS.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive,
-      nullptr, std::move(cloud_open_metrics_)));
+      std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
   // Expect that there was a notification.
   EXPECT_FALSE(notification_message_.empty());
@@ -2023,7 +2022,7 @@
   // Open file directly from ODFS.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), file_urls, ash::cloud_upload::CloudProvider::kOneDrive,
-      nullptr, std::move(cloud_open_metrics_)));
+      std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
   // Expect that there was a notification.
   EXPECT_FALSE(notification_message_.empty());
@@ -2070,7 +2069,7 @@
   // Open the file indirectly from Android OneDrive (via ODFS).
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), {android_onedrive_url},
-      ash::cloud_upload::CloudProvider::kOneDrive, nullptr,
+      ash::cloud_upload::CloudProvider::kOneDrive,
       std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
@@ -2124,7 +2123,7 @@
   // will fail as the email accounts don't match.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), {android_onedrive_url},
-      ash::cloud_upload::CloudProvider::kOneDrive, nullptr,
+      ash::cloud_upload::CloudProvider::kOneDrive,
       std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
@@ -2167,7 +2166,7 @@
   // will fail as there is not an equivalent ODFS file path.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), {android_onedrive_url},
-      ash::cloud_upload::CloudProvider::kOneDrive, nullptr,
+      ash::cloud_upload::CloudProvider::kOneDrive,
       std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
@@ -2215,7 +2214,7 @@
   // will fail as there is not an equivalent ODFS file path.
   auto task = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
       profile(), {android_onedrive_url},
-      ash::cloud_upload::CloudProvider::kOneDrive, nullptr,
+      ash::cloud_upload::CloudProvider::kOneDrive,
       std::move(cloud_open_metrics_)));
   task->OpenOrMoveFiles();
 
@@ -2241,7 +2240,7 @@
 IN_PROC_BROWSER_TEST_F(OneDriveTest, FileInOneDriveOpensSetUpDialog) {
   // Do this before SetUpTest creates a FakeWebAppPublisher which would
   // intercept Files app launching.
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait();
+  LaunchFilesAppAndWait();
 
   // Creates a fake ODFS with a test file.
   SetUpTest();
@@ -2260,8 +2259,7 @@
   navigation_observer_dialog.StartWatchingNewWebContents();
 
   // Triggers setup flow.
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, modal_parent,
-                  base::DoNothing());
+  ExecuteFileTask(profile(), open_in_office_task, file_urls, base::DoNothing());
 
   // Wait for setup flow dialog to open.
   navigation_observer_dialog.Wait();
@@ -2290,11 +2288,10 @@
       expected_dialog_URL);
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait();
+  LaunchFilesAppAndWait();
 
   // Triggers setup flow.
-  ExecuteFileTask(profile(), open_in_office_task, file_urls, modal_parent,
-                  base::DoNothing());
+  ExecuteFileTask(profile(), open_in_office_task, file_urls, base::DoNothing());
 
   // Wait for setup flow dialog to open.
   navigation_observer_dialog.Wait();
diff --git a/chrome/browser/ash/file_manager/office_file_tasks.cc b/chrome/browser/ash/file_manager/office_file_tasks.cc
index acfa1647..9ea10b4d 100644
--- a/chrome/browser/ash/file_manager/office_file_tasks.cc
+++ b/chrome/browser/ash/file_manager/office_file_tasks.cc
@@ -185,11 +185,10 @@
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
   if (!drive::util::IsDriveEnabledForProfile(profile)) {
     return GetUserFallbackChoice(
-        profile, task, file_urls, modal_parent,
+        profile, task, file_urls,
         ash::office_fallback::FallbackReason::kDriveDisabled,
         std::move(cloud_open_metrics));
   }
@@ -200,7 +199,7 @@
       opt_fallback_reason =
           DriveConnectionStatusToFallbackReason(drive_connection_status);
   if (opt_fallback_reason) {
-    return GetUserFallbackChoice(profile, task, file_urls, modal_parent,
+    return GetUserFallbackChoice(profile, task, file_urls,
                                  opt_fallback_reason.value(),
                                  std::move(cloud_open_metrics));
   }
@@ -210,31 +209,30 @@
   if (!integration_service || !integration_service->IsMounted() ||
       !integration_service->GetDriveFsInterface()) {
     return GetUserFallbackChoice(
-        profile, task, file_urls, modal_parent,
+        profile, task, file_urls,
         ash::office_fallback::FallbackReason::kDriveFsInterfaceError,
         std::move(cloud_open_metrics));
   }
 
   return ash::cloud_upload::CloudOpenTask::Execute(
       profile, file_urls, ash::cloud_upload::CloudProvider::kGoogleDrive,
-      modal_parent, std::move(cloud_open_metrics));
+      std::move(cloud_open_metrics));
 }
 
 bool ExecuteOpenInOfficeTask(
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
   if (content::GetNetworkConnectionTracker()->IsOffline()) {
-    return GetUserFallbackChoice(profile, task, file_urls, modal_parent,
+    return GetUserFallbackChoice(profile, task, file_urls,
                                  ash::office_fallback::FallbackReason::kOffline,
                                  std::move(cloud_open_metrics));
   }
 
   return ash::cloud_upload::CloudOpenTask::Execute(
       profile, file_urls, ash::cloud_upload::CloudProvider::kOneDrive,
-      modal_parent, std::move(cloud_open_metrics));
+      std::move(cloud_open_metrics));
 }
 
 void LaunchQuickOffice(Profile* profile,
@@ -244,7 +242,7 @@
       kActionIdQuickOffice);
 
   ExecuteFileTask(
-      profile, quick_office_task, file_urls, /* modal_parent */ nullptr,
+      profile, quick_office_task, file_urls,
       base::BindOnce(
           [](extensions::api::file_manager_private::TaskResult result,
              std::string error_message) {
@@ -264,7 +262,6 @@
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
     ash::office_fallback::FallbackReason fallback_reason,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics,
     std::optional<const std::string> choice) {
   if (!choice.has_value()) {
@@ -302,10 +299,10 @@
     // Only the last open result is recorded: when the user either selects
     // QO or cancels.
     if (IsWebDriveOfficeTask(task)) {
-      ExecuteWebDriveOfficeTask(profile, task, file_urls, modal_parent,
+      ExecuteWebDriveOfficeTask(profile, task, file_urls,
                                 std::move(cloud_open_metrics));
     } else if (IsOpenInOfficeTask(task)) {
-      ExecuteOpenInOfficeTask(profile, task, file_urls, modal_parent,
+      ExecuteOpenInOfficeTask(profile, task, file_urls,
                               std::move(cloud_open_metrics));
     }
   } else if (choice.value() == ash::office_fallback::kDialogChoiceCancel) {
@@ -327,7 +324,6 @@
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     ash::office_fallback::FallbackReason fallback_reason,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics) {
   // TODO(b/242685536) Add support for multi-file
@@ -335,9 +331,9 @@
   // `OnDialogChoiceReceived()` can open multiple files.
   std::vector<storage::FileSystemURL> first_url{file_urls.front()};
 
-  ash::office_fallback::DialogChoiceCallback callback = base::BindOnce(
-      &OnDialogChoiceReceived, profile, task, first_url, fallback_reason,
-      modal_parent, std::move(cloud_open_metrics));
+  ash::office_fallback::DialogChoiceCallback callback =
+      base::BindOnce(&OnDialogChoiceReceived, profile, task, first_url,
+                     fallback_reason, std::move(cloud_open_metrics));
 
   // If QuickOffice is not installed, don't launch dialog.
   if (!IsQuickOfficeInstalled(profile)) {
diff --git a/chrome/browser/ash/file_manager/office_file_tasks.h b/chrome/browser/ash/file_manager/office_file_tasks.h
index 237e851..b5f1db1 100644
--- a/chrome/browser/ash/file_manager/office_file_tasks.h
+++ b/chrome/browser/ash/file_manager/office_file_tasks.h
@@ -12,7 +12,6 @@
 #include "base/files/file_path.h"
 #include "base/time/time.h"
 #include "chrome/browser/ui/webui/ash/office_fallback/office_fallback_dialog.h"
-#include "ui/gfx/native_widget_types.h"
 
 class Profile;
 
@@ -109,7 +108,6 @@
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics);
 
 // Open files with Office365.
@@ -117,7 +115,6 @@
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics);
 
 // Executes QuickOffice file handler for each element of |file_urls|.
@@ -133,7 +130,6 @@
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
     ash::office_fallback::FallbackReason fallback_reason,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics,
     std::optional<const std::string> choice);
 
@@ -143,7 +139,6 @@
     Profile* profile,
     const TaskDescriptor& task,
     const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent,
     ash::office_fallback::FallbackReason failure_reason,
     std::unique_ptr<ash::cloud_upload::CloudOpenMetrics> cloud_open_metrics);
 
diff --git a/chrome/browser/ash/file_manager/open_util.cc b/chrome/browser/ash/file_manager/open_util.cc
index 4ef20d28..5af88a9 100644
--- a/chrome/browser/ash/file_manager/open_util.cc
+++ b/chrome/browser/ash/file_manager/open_util.cc
@@ -52,13 +52,11 @@
   storage::FileSystemContext* file_system_context =
       GetFileManagerFileSystemContext(profile);
 
-  // There is no Files app window for spawned WebUI to be modal to.
-  gfx::NativeWindow modal_parent = gfx::NativeWindow();
   file_tasks::ExecuteFileTask(
       profile, task,
       std::vector<FileSystemURL>(
           1, file_system_context->CrackURLInFirstPartyContext(url)),
-      modal_parent, base::BindOnce(&IgnoreFileTaskExecuteResult));
+      base::BindOnce(&IgnoreFileTaskExecuteResult));
 }
 
 // Opens the file manager for the specified |url|. Used to implement
diff --git a/chrome/browser/ash/file_manager/virtual_file_tasks.cc b/chrome/browser/ash/file_manager/virtual_file_tasks.cc
index ef36d04..09b1c87 100644
--- a/chrome/browser/ash/file_manager/virtual_file_tasks.cc
+++ b/chrome/browser/ash/file_manager/virtual_file_tasks.cc
@@ -108,13 +108,12 @@
 
 bool ExecuteVirtualTask(Profile* profile,
                         const TaskDescriptor& task,
-                        const std::vector<FileSystemURL>& file_urls,
-                        gfx::NativeWindow modal_parent) {
+                        const std::vector<FileSystemURL>& file_urls) {
   auto* virtual_task = FindVirtualTask(task);
   if (!virtual_task || !virtual_task->IsEnabled(profile)) {
     return false;
   }
-  return virtual_task->Execute(profile, task, file_urls, modal_parent);
+  return virtual_task->Execute(profile, task, file_urls);
 }
 
 bool IsVirtualTask(const TaskDescriptor& task) {
diff --git a/chrome/browser/ash/file_manager/virtual_file_tasks.h b/chrome/browser/ash/file_manager/virtual_file_tasks.h
index f56bc9d..18efe01 100644
--- a/chrome/browser/ash/file_manager/virtual_file_tasks.h
+++ b/chrome/browser/ash/file_manager/virtual_file_tasks.h
@@ -34,8 +34,7 @@
   // started running, if there are async steps.
   virtual bool Execute(Profile* profile,
                        const TaskDescriptor& task,
-                       const std::vector<FileSystemURL>& file_urls,
-                       gfx::NativeWindow modal_parent) const = 0;
+                       const std::vector<FileSystemURL>& file_urls) const = 0;
   // Whether this task should be included in |FindVirtualTasks()|. This can be
   // used to disable tasks based on a flag or other runtime conditions.
   virtual bool IsEnabled(Profile* profile) const = 0;
@@ -76,8 +75,7 @@
 // Run |task| by calling |Execute()| on the associated VirtualTask.
 bool ExecuteVirtualTask(Profile* profile,
                         const TaskDescriptor& task,
-                        const std::vector<FileSystemURL>& file_urls,
-                        gfx::NativeWindow modal_parent);
+                        const std::vector<FileSystemURL>& file_urls);
 
 // Whether |task| is a virtual task and can be executed using
 // |ExecuteVirtualTask()|. Returns true for disabled tasks, too.
diff --git a/chrome/browser/ash/file_manager/virtual_file_tasks_unittest.cc b/chrome/browser/ash/file_manager/virtual_file_tasks_unittest.cc
index a58738f..8490a848 100644
--- a/chrome/browser/ash/file_manager/virtual_file_tasks_unittest.cc
+++ b/chrome/browser/ash/file_manager/virtual_file_tasks_unittest.cc
@@ -91,8 +91,7 @@
 TEST_F(VirtualFileTasksTest, ExecuteVirtualTask_WrongApp) {
   TaskDescriptor wrong_app = {"random_app", TASK_TYPE_WEB_APP, task1->id()};
   bool result =
-      ExecuteVirtualTask(/*profile=*/nullptr, wrong_app, /*file_urls=*/{},
-                         /*modal_parent=*/nullptr);
+      ExecuteVirtualTask(/*profile=*/nullptr, wrong_app, /*file_urls=*/{});
   ASSERT_FALSE(result);
   ASSERT_EQ(task1_executed_, 0);
 }
@@ -100,9 +99,8 @@
 TEST_F(VirtualFileTasksTest, ExecuteVirtualTask_WrongActionId) {
   TaskDescriptor wrong_action_id = {kFileManagerSwaAppId, TASK_TYPE_WEB_APP,
                                     "https://app/wrongaction"};
-  bool result =
-      ExecuteVirtualTask(/*profile=*/nullptr, wrong_action_id, /*file_urls=*/{},
-                         /*modal_parent=*/nullptr);
+  bool result = ExecuteVirtualTask(/*profile=*/nullptr, wrong_action_id,
+                                   /*file_urls=*/{});
   ASSERT_FALSE(result);
   ASSERT_EQ(task1_executed_, 0);
 }
@@ -111,8 +109,7 @@
   TaskDescriptor ok_task = {kFileManagerSwaAppId, TASK_TYPE_WEB_APP,
                             task1->id()};
   bool result =
-      ExecuteVirtualTask(/*profile=*/nullptr, ok_task, /*file_urls=*/{},
-                         /*modal_parent=*/nullptr);
+      ExecuteVirtualTask(/*profile=*/nullptr, ok_task, /*file_urls=*/{});
   ASSERT_TRUE(result);
   ASSERT_EQ(task1_executed_, 1);
 }
@@ -121,8 +118,7 @@
   TaskDescriptor disabled_task = {kFileManagerSwaAppId, TASK_TYPE_WEB_APP,
                                   task2->id()};
   bool result =
-      ExecuteVirtualTask(/*profile=*/nullptr, disabled_task, /*file_urls=*/{},
-                         /*modal_parent=*/nullptr);
+      ExecuteVirtualTask(/*profile=*/nullptr, disabled_task, /*file_urls=*/{});
   ASSERT_FALSE(result);
   ASSERT_EQ(task2_executed_, 0);
 }
@@ -131,8 +127,7 @@
   TaskDescriptor execute_false = {kFileManagerSwaAppId, TASK_TYPE_WEB_APP,
                                   task3->id()};
   bool result =
-      ExecuteVirtualTask(/*profile=*/nullptr, execute_false, /*file_urls=*/{},
-                         /*modal_parent=*/nullptr);
+      ExecuteVirtualTask(/*profile=*/nullptr, execute_false, /*file_urls=*/{});
   ASSERT_FALSE(result);
   ASSERT_EQ(task3_executed_, 1);
 }
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.cc b/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.cc
index 4b3d89c..d269f00 100644
--- a/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.cc
+++ b/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.cc
@@ -17,10 +17,10 @@
 
 FakeVirtualTask::~FakeVirtualTask() = default;
 
-bool FakeVirtualTask::Execute(Profile* profile,
-                              const TaskDescriptor& task,
-                              const std::vector<FileSystemURL>& file_urls,
-                              gfx::NativeWindow modal_parent) const {
+bool FakeVirtualTask::Execute(
+    Profile* profile,
+    const TaskDescriptor& task,
+    const std::vector<FileSystemURL>& file_urls) const {
   return execute_cb_ ? execute_cb_.Run() : true;
 }
 
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.h b/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.h
index 9791dee..672c62fa 100644
--- a/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.h
+++ b/chrome/browser/ash/file_manager/virtual_tasks/fake_virtual_task.h
@@ -26,8 +26,7 @@
 
   bool Execute(Profile* profile,
                const TaskDescriptor& task,
-               const std::vector<FileSystemURL>& file_urls,
-               gfx::NativeWindow modal_parent) const override;
+               const std::vector<FileSystemURL>& file_urls) const override;
   bool IsEnabled(Profile* profile) const override;
   bool Matches(const std::vector<extensions::EntryInfo>& entries,
                const std::vector<GURL>& file_urls,
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.cc b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.cc
index d3be1711..d770e37 100644
--- a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.cc
+++ b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.cc
@@ -25,7 +25,6 @@
 #include "storage/browser/file_system/file_system_url.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
-#include "ui/gfx/native_widget_types.h"
 #include "url/gurl.h"
 
 namespace file_manager::file_tasks {
@@ -57,8 +56,7 @@
 bool InstallIsolatedWebAppVirtualTask::Execute(
     Profile* profile,
     const TaskDescriptor& task,
-    const std::vector<storage::FileSystemURL>& file_urls,
-    gfx::NativeWindow modal_parent) const {
+    const std::vector<storage::FileSystemURL>& file_urls) const {
   if (file_urls.empty()) {
     return false;
   }
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.h b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.h
index 90a6c59..76914846 100644
--- a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.h
+++ b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "chrome/browser/ash/file_manager/virtual_file_tasks.h"
-#include "ui/gfx/native_widget_types.h"
 
 class GURL;
 class Profile;
@@ -36,10 +35,10 @@
 
   GURL icon_url() const override;
 
-  bool Execute(Profile* profile,
-               const TaskDescriptor& task,
-               const std::vector<storage::FileSystemURL>& file_urls,
-               gfx::NativeWindow modal_parent) const override;
+  bool Execute(
+      Profile* profile,
+      const TaskDescriptor& task,
+      const std::vector<storage::FileSystemURL>& file_urls) const override;
 };
 
 }  // namespace file_manager::file_tasks
diff --git a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task_unittest.cc b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task_unittest.cc
index f9dba16..df733718 100644
--- a/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task_unittest.cc
+++ b/chrome/browser/ash/file_manager/virtual_tasks/install_isolated_web_app_virtual_task_unittest.cc
@@ -88,7 +88,7 @@
 
     return ExecuteVirtualTask(
         &profile_, {kFileManagerSwaAppId, TASK_TYPE_WEB_APP, task_.id()},
-        file_system_urls, /*modal_parent=*/nullptr);
+        file_system_urls);
   }
 
  private:
diff --git a/chrome/browser/ash/guest_os/public/guest_os_service_factory.cc b/chrome/browser/ash/guest_os/public/guest_os_service_factory.cc
index 46c3464..1cd019f 100644
--- a/chrome/browser/ash/guest_os/public/guest_os_service_factory.cc
+++ b/chrome/browser/ash/guest_os/public/guest_os_service_factory.cc
@@ -40,12 +40,13 @@
 
 GuestOsServiceFactory::~GuestOsServiceFactory() = default;
 
-KeyedService* GuestOsServiceFactory::BuildServiceInstanceFor(
+std::unique_ptr<KeyedService>
+GuestOsServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
   if (!profile)
     return nullptr;
-  return new GuestOsService(profile);
+  return std::make_unique<GuestOsService>(profile);
 }
 
 }  // namespace guest_os
diff --git a/chrome/browser/ash/guest_os/public/guest_os_service_factory.h b/chrome/browser/ash/guest_os/public/guest_os_service_factory.h
index 723e5a8..b20b3d3 100644
--- a/chrome/browser/ash/guest_os/public/guest_os_service_factory.h
+++ b/chrome/browser/ash/guest_os/public/guest_os_service_factory.h
@@ -29,7 +29,7 @@
   ~GuestOsServiceFactory() override;
 
   // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
       content::BrowserContext* context) const override;
 };
 
diff --git a/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc b/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
index 4eaef4d8..1588bc5 100644
--- a/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
+++ b/chrome/browser/ash/wallpaper_handlers/sea_pen_utils.cc
@@ -10,6 +10,8 @@
 
 namespace wallpaper_handlers {
 
+// Generated by sea_pen_client_generator.py, do not edit.
+// TODO(b/318565684): Move generated code to a separate file.
 namespace {
 
 std::string TemplateIdToString(
@@ -23,6 +25,18 @@
       return "landscape";
     case ash::personalization_app::mojom::SeaPenTemplateId::kScifi:
       return "scifi";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kArt:
+      return "art";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kCharacters:
+      return "characters";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kTerrain:
+      return "terrain";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kCurious:
+      return "curious";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kDreamscapes:
+      return "dreamscapes";
+    case ash::personalization_app::mojom::SeaPenTemplateId::kTranslucent:
+      return "translucent";
   }
 }
 
@@ -46,6 +60,41 @@
       return "<scifi_feature>";
     case ash::personalization_app::mojom::SeaPenTemplateChip::kScifiColor:
       return "<scifi_color>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kArtFeature:
+      return "<art_feature>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kArtMovement:
+      return "<art_movement>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kCharactersColor:
+      return "<characters_color>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::
+        kCharactersSubjects:
+      return "<characters_subjects>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::
+        kCharactersBackground:
+      return "<characters_background>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kTerrainFeature:
+      return "<terrain_feature>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kTerrainColor:
+      return "<terrain_color>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kCuriousSubject:
+      return "<curious_subject>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kCuriousFeature:
+      return "<curious_feature>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kCuriousColor:
+      return "<curious_color>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::
+        kDreamscapesObject:
+      return "<dreamscapes_object>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::
+        kDreamscapesMaterial:
+      return "<dreamscapes_material>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::
+        kDreamscapesColors:
+      return "<dreamscapes_colors>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kTranslucentItem:
+      return "<translucent_item>";
+    case ash::personalization_app::mojom::SeaPenTemplateChip::kTranslucentColor:
+      return "<translucent_color>";
   }
 }
 
@@ -305,6 +354,870 @@
     case ash::personalization_app::mojom::SeaPenTemplateOption::
         kScifiColorNeutral:
       return "neutral";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureCanyon:
+      return "canyon";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureMountain:
+      return "mountain";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureBeach:
+      return "beach";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::kArtFeatureCave:
+      return "cave";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureCliff:
+      return "cliff";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureForest:
+      return "forest";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureGlacier:
+      return "glacier";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureIsland:
+      return "island";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureJungle:
+      return "jungle";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::kArtFeatureLake:
+      return "lake";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureMeadow:
+      return "meadow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureOcean:
+      return "ocean";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureRiver:
+      return "river";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::kArtFeatureDune:
+      return "dune";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureSwamp:
+      return "swamp";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureValley:
+      return "valley";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureWaterfall:
+      return "waterfall";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureField:
+      return "field";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureCityscape:
+      return "cityscape";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtFeatureVillage:
+      return "village";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementAvantGarde:
+      return "avant_garde";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementRealism:
+      return "realism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementExpressionism:
+      return "expressionism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementImpressionism:
+      return "impressionism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementPostImpressionism:
+      return "post_impressionism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementArtNouveau:
+      return "art_nouveau";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementBaroque:
+      return "baroque";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementBauhaus:
+      return "bauhaus";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementClassicism:
+      return "classicism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementWatercolor:
+      return "watercolor";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementAbstract:
+      return "abstract";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementPointillism:
+      return "pointillism";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementGraphicDesign:
+      return "graphic_design";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kArtMovementModernArt:
+      return "modern_art";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorYellow:
+      return "yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorPink:
+      return "pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorRed:
+      return "red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorBlue:
+      return "blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorIndigo:
+      return "indigo";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorGreen:
+      return "green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorEmerald:
+      return "emerald";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorTeal:
+      return "teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorCyan:
+      return "cyan";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorPurple:
+      return "purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorBrown:
+      return "brown";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorGold:
+      return "gold";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorBurntOrange:
+      return "burnt_orange";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorRust:
+      return "rust";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorOlive:
+      return "olive";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorGray:
+      return "gray";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorViolet:
+      return "violet";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorWhite:
+      return "white";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorBeige:
+      return "beige";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorIvory:
+      return "ivory";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorCream:
+      return "cream";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorMagenta:
+      return "magenta";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorLimeGreen:
+      return "lime_green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorFuschia:
+      return "fuschia";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorElectricBlue:
+      return "electric_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorHotPink:
+      return "hot_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorNeonGreen:
+      return "neon_green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorSkyBlue:
+      return "sky_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorElectricPurple:
+      return "electric_purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorFireEngineRed:
+      return "fire_engine_red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorNeonPink:
+      return "neon_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorChartreuse:
+      return "chartreuse";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorCobalt:
+      return "cobalt";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorLemonYellow:
+      return "lemon_yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorCoralPink:
+      return "coral_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorVibrantViolet:
+      return "vibrant_violet";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersColorPeacockBlue:
+      return "peacock_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsLemons:
+      return "lemons";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsFlowers:
+      return "flowers";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsApples:
+      return "apples";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsCherries:
+      return "cherries";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsOranges:
+      return "oranges";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPineapples:
+      return "pineapples";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsStrawberries:
+      return "strawberries";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsWatermelons:
+      return "watermelons";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPotatoes:
+      return "potatoes";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsSushi:
+      return "sushi";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBaconAndEggs:
+      return "bacon_and_eggs";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPizza:
+      return "pizza";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsHotDogs:
+      return "hot_dogs";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsHamburgers:
+      return "hamburgers";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsRamen:
+      return "ramen";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsTacos:
+      return "tacos";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBunnies:
+      return "bunnies";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsCats:
+      return "cats";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsDogs:
+      return "dogs";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsKoalas:
+      return "koalas";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPandas:
+      return "pandas";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPenguins:
+      return "penguins";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPigs:
+      return "pigs";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsSloths:
+      return "sloths";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsPonies:
+      return "ponies";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsElephants:
+      return "elephants";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsFoxes:
+      return "foxes";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsOwls:
+      return "owls";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsCrabs:
+      return "crabs";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBees:
+      return "bees";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsButterflies:
+      return "butterflies";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBicycles:
+      return "bicycles";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBoats:
+      return "boats";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsBooks:
+      return "books";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsCutlery:
+      return "cutlery";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsUmbrellas:
+      return "umbrellas";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersSubjectsInstruments:
+      return "instruments";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundPurple:
+      return "purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundBlue:
+      return "blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundIndigo:
+      return "indigo";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundGreen:
+      return "green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundEmerald:
+      return "emerald";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundTeal:
+      return "teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundCyan:
+      return "cyan";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundBrown:
+      return "brown";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundGold:
+      return "gold";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundRed:
+      return "red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundBurntOrange:
+      return "burnt_orange";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundRust:
+      return "rust";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundOlive:
+      return "olive";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundPink:
+      return "pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundGray:
+      return "gray";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundYellow:
+      return "yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundViolet:
+      return "violet";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundWhite:
+      return "white";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundBeige:
+      return "beige";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundIvory:
+      return "ivory";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundCream:
+      return "cream";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundMagenta:
+      return "magenta";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundLimeGreen:
+      return "lime_green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundFuschia:
+      return "fuschia";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundElectricBlue:
+      return "electric_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundHotPink:
+      return "hot_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundNeonGreen:
+      return "neon_green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundSkyBlue:
+      return "sky_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundElectricPurple:
+      return "electric_purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundFireEngineRed:
+      return "fire_engine_red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundNeonPink:
+      return "neon_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundChartreuse:
+      return "chartreuse";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundCobalt:
+      return "cobalt";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundLemonYellow:
+      return "lemon_yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundCoralPink:
+      return "coral_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundVibrantViolet:
+      return "vibrant_violet";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCharactersBackgroundPeacockBlue:
+      return "peacock_blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureSaltLake:
+      return "salt_lake";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureRiver:
+      return "river";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureNorthernLights:
+      return "northern_lights";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureWhiteDunes:
+      return "white_dunes";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureClayHills:
+      return "clay_hills";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureSandyLagoons:
+      return "sandy_lagoons";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureMountains:
+      return "mountains";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureBioluminescentBeach:
+      return "bioluminescent_beach";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureFireflyForest:
+      return "firefly_forest";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainFeatureSandDunes:
+      return "sand_dunes";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorPink:
+      return "pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorTeal:
+      return "teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorWhite:
+      return "white";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorPurple:
+      return "purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorBlue:
+      return "blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorYellow:
+      return "yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorMaroonPink:
+      return "maroon_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorBluePurple:
+      return "blue_purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorPinkYellow:
+      return "pink_yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTerrainColorBluePink:
+      return "blue_pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectCherryBlossoms:
+      return "cherry_blossoms";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectJasmineFlowers:
+      return "jasmine_flowers";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectDaisies:
+      return "daisies";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectTulips:
+      return "tulips";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectCarnations:
+      return "carnations";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectDaffodils:
+      return "daffodils";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectForgetMeNots:
+      return "forget_me_nots";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectSunflowers:
+      return "sunflowers";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectBougainvilleas:
+      return "bougainvilleas";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectAirPlants:
+      return "air_plants";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousSubjectSucculents:
+      return "succulents";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureAlpineLake:
+      return "alpine_lake";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureGalaxy:
+      return "galaxy";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureSandDunes:
+      return "sand_dunes";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureSwamp:
+      return "swamp";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureBeach:
+      return "beach";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureMountains:
+      return "mountains";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureRiver:
+      return "river";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousFeatureWaterfall:
+      return "waterfall";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorBlue:
+      return "blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorRed:
+      return "red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorYellow:
+      return "yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorGreen:
+      return "green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorPurple:
+      return "purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorOrange:
+      return "orange";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorPink:
+      return "pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorBrown:
+      return "brown";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorBlack:
+      return "black";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorTurquoise:
+      return "turquoise";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorMagenta:
+      return "magenta";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorLavender:
+      return "lavender";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorMaroon:
+      return "maroon";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorNavy:
+      return "navy";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorOlive:
+      return "olive";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorCoral:
+      return "coral";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorCream:
+      return "cream";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorIndigo:
+      return "indigo";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kCuriousColorFuchsia:
+      return "fuchsia";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectBicycle:
+      return "bicycle";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectCastle:
+      return "castle";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectBuilding:
+      return "building";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectBoat:
+      return "boat";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectLamp:
+      return "lamp";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectTable:
+      return "table";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectBridge:
+      return "bridge";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectLighthouse:
+      return "lighthouse";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectPagoda:
+      return "pagoda";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectPalace:
+      return "palace";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectTower:
+      return "tower";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesObjectUfo:
+      return "UFO";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialFlowers:
+      return "flowers";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialSilk:
+      return "silk";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialFelt:
+      return "felt";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialBurlap:
+      return "burlap";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialChiffon:
+      return "chiffon";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialCotton:
+      return "cotton";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialFur:
+      return "fur";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialLace:
+      return "lace";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialLinen:
+      return "linen";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialOrganza:
+      return "organza";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialTulle:
+      return "tulle";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialWool:
+      return "wool";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialYarn:
+      return "yarn";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialFleece:
+      return "fleece";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialClay:
+      return "clay";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialStone:
+      return "stone";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialWood:
+      return "wood";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialAmethyst:
+      return "amethyst";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialLapisLuzuli:
+      return "lapis_luzuli";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialObsidian:
+      return "obsidian";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialOpal:
+      return "opal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesMaterialSapphire:
+      return "sapphire";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsPinkPurple:
+      return "pink_purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsCoralTan:
+      return "coral_tan";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsCreamOrange:
+      return "cream_orange";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsBlueIndigo:
+      return "blue_indigo";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsGreenTeal:
+      return "green_teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsBurgundyMaroon:
+      return "burgundy_maroon";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kDreamscapesColorsYellowTeal:
+      return "yellow_teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemApple:
+      return "apple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemAzalea:
+      return "azalea";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemBegonia:
+      return "begonia";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemBluebell:
+      return "bluebell";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemCherryBlossom:
+      return "cherry_blossom";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemChrysanthemum:
+      return "chrysanthemum";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemClemati:
+      return "clemati";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemDaffodil:
+      return "daffodil";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemDaisy:
+      return "daisy";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemDandelion:
+      return "dandelion";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemRose:
+      return "rose";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemDogwood:
+      return "dogwood";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemHibiscus:
+      return "hibiscus";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemHydrangea:
+      return "hydrangea";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemLeaf:
+      return "leaf";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemLily:
+      return "lily";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPansy:
+      return "pansy";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPear:
+      return "pear";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPeony:
+      return "peony";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPhilodendron:
+      return "philodendron";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPoppy:
+      return "poppy";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemSunflower:
+      return "sunflower";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemPea:
+      return "pea";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemTulip:
+      return "tulip";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemButterfly:
+      return "butterfly";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentItemDragonfly:
+      return "dragonfly";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorPink:
+      return "pink";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorBlue:
+      return "blue";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorIndigo:
+      return "indigo";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorGreen:
+      return "green";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorEmerald:
+      return "emerald";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorTeal:
+      return "teal";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorCyan:
+      return "cyan";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorPurple:
+      return "purple";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorGold:
+      return "gold";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorRed:
+      return "red";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorRust:
+      return "rust";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorOlive:
+      return "olive";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorGray:
+      return "gray";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorYellow:
+      return "yellow";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorViolet:
+      return "violet";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorIvory:
+      return "ivory";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorMagenta:
+      return "magenta";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorPeach:
+      return "peach";
+    case ash::personalization_app::mojom::SeaPenTemplateOption::
+        kTranslucentColorBlack:
+      return "black";
   }
 }
 
@@ -314,88 +1227,271 @@
   auto options = query->options;
   switch (id) {
     case ash::personalization_app::mojom::SeaPenTemplateId::kFlower: {
-      auto flower_type = options
-                             .find(ash::personalization_app::mojom::
-                                       SeaPenTemplateChip::kFlowerType)
-                             ->second;
-      auto flower_color = options
-                              .find(ash::personalization_app::mojom::
-                                        SeaPenTemplateChip::kFlowerColor)
-                              ->second;
-      return (flower_type >= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kFlowerTypeRose &&
-              flower_type <= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kFlowerTypeHydrangeas &&
-              flower_color >= ash::personalization_app::mojom::
-                                  SeaPenTemplateOption::kFlowerColorPink &&
-              flower_color <= ash::personalization_app::mojom::
-                                  SeaPenTemplateOption::kFlowerColorRed);
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kFlowerColor)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kFlowerColorPink ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kFlowerColorRed) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kFlowerType)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kFlowerTypeRose ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kFlowerTypeHydrangeas) {
+        return false;
+      }
+      return true;
     }
     case ash::personalization_app::mojom::SeaPenTemplateId::kMineral: {
-      auto mineral_name = options
-                              .find(ash::personalization_app::mojom::
-                                        SeaPenTemplateChip::kMineralName)
-                              ->second;
-      auto mineral_color = options
-                               .find(ash::personalization_app::mojom::
-                                         SeaPenTemplateChip::kMineralColor)
-                               ->second;
-      return (mineral_name >= ash::personalization_app::mojom::
-                                  SeaPenTemplateOption::kMineralNameAgate &&
-              mineral_name <=
-                  ash::personalization_app::mojom::SeaPenTemplateOption::
-                      kMineralNameTourmaline &&
-              mineral_color >= ash::personalization_app::mojom::
-                                   SeaPenTemplateOption::kMineralColorWarm &&
-              mineral_color <= ash::personalization_app::mojom::
-                                   SeaPenTemplateOption::kMineralColorGray);
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kMineralName)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kMineralNameAgate ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kMineralNameTourmaline) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kMineralColor)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kMineralColorWarm ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kMineralColorGray) {
+        return false;
+      }
+      return true;
     }
     case ash::personalization_app::mojom::SeaPenTemplateId::kLandscape: {
-      auto landscape_biome = options
-                                 .find(ash::personalization_app::mojom::
-                                           SeaPenTemplateChip::kLandscapeBiome)
-                                 ->second;
-      auto landscape_lighting =
-          options
-              .find(ash::personalization_app::mojom::SeaPenTemplateChip::
-                        kLandscapeLighting)
-              ->second;
-      return (
-          landscape_biome >= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kLandscapeBiomeTaiga &&
-          landscape_biome <= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kLandscapeBiomeForest &&
-          landscape_lighting >=
-              ash::personalization_app::mojom::SeaPenTemplateOption::
-                  kLandscapeLightingDiffuse &&
-          landscape_lighting <=
-              ash::personalization_app::mojom::SeaPenTemplateOption::
-                  kLandscapeLightingMidday);
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kLandscapeBiome)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kLandscapeBiomeTaiga ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kLandscapeBiomeForest) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kLandscapeLighting)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kLandscapeLightingDiffuse ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kLandscapeLightingMidday) {
+        return false;
+      }
+      return true;
     }
     case ash::personalization_app::mojom::SeaPenTemplateId::kScifi: {
-      auto scifi_feature = options
-                               .find(ash::personalization_app::mojom::
-                                         SeaPenTemplateChip::kScifiFeature)
-                               ->second;
-      auto scifi_color = options
-                             .find(ash::personalization_app::mojom::
-                                       SeaPenTemplateChip::kScifiColor)
-                             ->second;
-      return (scifi_feature >= ash::personalization_app::mojom::
-                                   SeaPenTemplateOption::kScifiFeatureStreet &&
-              scifi_feature <=
-                  ash::personalization_app::mojom::SeaPenTemplateOption::
-                      kScifiFeatureUnderwater &&
-              scifi_color >= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kScifiColorEarthy &&
-              scifi_color <= ash::personalization_app::mojom::
-                                 SeaPenTemplateOption::kScifiColorNeutral);
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kScifiFeature)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kScifiFeatureStreet ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kScifiFeatureUnderwater) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kScifiColor)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kScifiColorEarthy ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kScifiColorNeutral) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kArt: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kArtFeature)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kArtFeatureCanyon ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kArtFeatureVillage) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kArtMovement)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kArtMovementAvantGarde ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kArtMovementModernArt) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kCharacters: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCharactersColor)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersColorYellow ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersColorPeacockBlue) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCharactersSubjects)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersSubjectsLemons ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersSubjectsInstruments) {
+        return false;
+      }
+      auto enum3 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCharactersBackground)
+                       ->second;
+      if (enum3 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersBackgroundPurple ||
+          enum3 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCharactersBackgroundPeacockBlue) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kTerrain: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kTerrainFeature)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTerrainFeatureSaltLake ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTerrainFeatureSandDunes) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kTerrainColor)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTerrainColorPink ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTerrainColorBluePink) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kCurious: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCuriousColor)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousColorBlue ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousColorFuchsia) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCuriousFeature)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousFeatureAlpineLake ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousFeatureWaterfall) {
+        return false;
+      }
+      auto enum3 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kCuriousSubject)
+                       ->second;
+      if (enum3 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousSubjectCherryBlossoms ||
+          enum3 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kCuriousSubjectSucculents) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kDreamscapes: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kDreamscapesObject)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesObjectBicycle ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesObjectUfo) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kDreamscapesMaterial)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesMaterialFlowers ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesMaterialSapphire) {
+        return false;
+      }
+      auto enum3 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kDreamscapesColors)
+                       ->second;
+      if (enum3 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesColorsPinkPurple ||
+          enum3 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kDreamscapesColorsYellowTeal) {
+        return false;
+      }
+      return true;
+    }
+    case ash::personalization_app::mojom::SeaPenTemplateId::kTranslucent: {
+      auto enum1 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kTranslucentItem)
+                       ->second;
+      if (enum1 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTranslucentItemApple ||
+          enum1 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTranslucentItemDragonfly) {
+        return false;
+      }
+      auto enum2 = options
+                       .find(ash::personalization_app::mojom::
+                                 SeaPenTemplateChip::kTranslucentColor)
+                       ->second;
+      if (enum2 < ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTranslucentColorPink ||
+          enum2 > ash::personalization_app::mojom::SeaPenTemplateOption::
+                      kTranslucentColorBlack) {
+        return false;
+      }
+      return true;
     }
   }
   return false;
 }
 
 }  // namespace
+// End of generated code.
 
 bool IsValidOutput(manta::proto::OutputData output,
                    const std::string_view source) {
diff --git a/chrome/browser/compose/chrome_compose_client_unittest.cc b/chrome/browser/compose/chrome_compose_client_unittest.cc
index 3483a89..891d31e 100644
--- a/chrome/browser/compose/chrome_compose_client_unittest.cc
+++ b/chrome/browser/compose/chrome_compose_client_unittest.cc
@@ -375,6 +375,7 @@
 
   EXPECT_EQ(compose::mojom::ComposeStatus::kOk, result->status);
   EXPECT_EQ("Cucumbers", result->result);
+  EXPECT_FALSE(result->on_device_evaluation_used);
 
   // Check that a user action for the Compose request was emitted.
   EXPECT_EQ(1, user_action_tester().GetActionCount(
@@ -405,10 +406,11 @@
                   OptimizationGuideModelExecutionResultStreamingCallback
                       callback) {
             // Start with a partial response.
-            callback.Run(
+            auto opt_guide_response =
                 OptimizationGuideResponse(ComposeResponse(true, "Cucu"),
-                                          /*is_complete=*/false),
-                nullptr);
+                                          /*is_complete=*/false);
+            opt_guide_response.provided_by_on_device = true;
+            callback.Run(std::move(opt_guide_response), nullptr);
             saved_callback = callback;
           })));
   ShowDialogAndBindMojo();
@@ -440,11 +442,14 @@
   EXPECT_TRUE(initial_state->compose_state->has_pending_request);
 
   // Then send the full response.
-  saved_callback.Run(
-      OptimizationGuideResponse(ComposeResponse(true, "Cucumbers")), nullptr);
+  auto full_response =
+      OptimizationGuideResponse(ComposeResponse(true, "Cucumbers"));
+  full_response.provided_by_on_device = true;
+  saved_callback.Run(full_response, nullptr);
   auto complete_result = test_future.Take();
   EXPECT_EQ(compose::mojom::ComposeStatus::kOk, complete_result->status);
   EXPECT_EQ("Cucumbers", complete_result->result);
+  EXPECT_TRUE(complete_result->on_device_evaluation_used);
 
   // Check that a single response result OK metric was emitted.
   histogram_tester.ExpectUniqueSample(compose::kComposeResponseStatus,
diff --git a/chrome/browser/compose/compose_dialog_browsertest.cc b/chrome/browser/compose/compose_dialog_browsertest.cc
index 53ed9ca0..1e0f4e2 100644
--- a/chrome/browser/compose/compose_dialog_browsertest.cc
+++ b/chrome/browser/compose/compose_dialog_browsertest.cc
@@ -52,7 +52,6 @@
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/compose/test2.html")));
   ASSERT_NE(nullptr, ChromeComposeClient::FromWebContents(web_contents));
-  ComposeEnabling::SetEnabledForTesting(true);
 
   // get point of element
   gfx::PointF textarea_center =
@@ -85,7 +84,6 @@
   ASSERT_TRUE(ui_test_utils::NavigateToURL(
       browser(), embedded_test_server()->GetURL("/compose/test2.html")));
   ASSERT_NE(nullptr, ChromeComposeClient::FromWebContents(web_contents));
-  ComposeEnabling::SetEnabledForTesting(true);
 
   // get point of element
   gfx::PointF textarea_center =
@@ -112,7 +110,6 @@
       browser(), embedded_test_server()->GetURL("/compose/test2.html")));
   ASSERT_NE(nullptr, ChromeComposeClient::FromWebContents(web_contents));
   auto* client = ChromeComposeClient::FromWebContents(web_contents);
-  client->GetComposeEnabling().SetEnabledForTesting(true);
 
   // get point of element
   gfx::PointF textarea_center =
diff --git a/chrome/browser/compose/compose_session.cc b/chrome/browser/compose/compose_session.cc
index 0f5fac94..d4819118 100644
--- a/chrome/browser/compose/compose_session.cc
+++ b/chrome/browser/compose/compose_session.cc
@@ -392,6 +392,7 @@
   auto ui_response = compose::mojom::ComposeResponse::New();
   ui_response->status = compose::mojom::ComposeStatus::kOk;
   ui_response->result = response->output();
+  ui_response->on_device_evaluation_used = result->provided_by_on_device;
   current_state_->response = ui_response->Clone();
 
   // Log successful response status.
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index 87e508d..a466e78 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -52,6 +52,7 @@
 #include "components/policy/core/common/policy_pref_names.h"
 #include "components/prefs/pref_service.h"
 #include "components/safe_browsing/buildflags.h"
+#include "components/safe_browsing/content/common/file_type_policies_test_util.h"
 #include "components/safe_search_api/safe_search_util.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/download_item_utils.h"
@@ -751,6 +752,34 @@
   }
 }
 
+TEST_F(ChromeDownloadManagerDelegateTest, DragAndDropDangerous) {
+#if BUILDFLAG(ENABLE_PLUGINS)
+  content::PluginService::GetInstance()->Init();
+#endif
+
+  GURL url("http://example.com/foo");
+  base::FilePath path(GetPathInDownloadDir("foo.evil_file_type"));
+  safe_browsing::FileTypePoliciesTestOverlay scoped_dangerous =
+      safe_browsing::ScopedMarkAllFilesDangerousForTesting();
+
+  std::unique_ptr<download::MockDownloadItem> download_item =
+      CreateActiveDownloadItem(0);
+  EXPECT_CALL(*download_item, GetURL()).WillRepeatedly(ReturnRef(url));
+  EXPECT_CALL(*download_item, GetDownloadSource())
+      .WillRepeatedly(Return(download::DownloadSource::DRAG_AND_DROP));
+  EXPECT_CALL(*download_item, GetForcedFilePath())
+      .WillRepeatedly(ReturnRef(path));
+  EXPECT_CALL(*delegate(), MockCheckDownloadUrl(_, _))
+      .WillRepeatedly(
+          Return(download::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT));
+
+  DetermineDownloadTargetResult result;
+  DetermineDownloadTarget(download_item.get(), &result);
+
+  EXPECT_EQ(DownloadFileType::DANGEROUS,
+            DownloadItemModel(download_item.get()).GetDangerLevel());
+}
+
 TEST_F(ChromeDownloadManagerDelegateTest, BlockedByPolicy) {
   const GURL kUrl("http://example.com/foo");
   const std::string kTargetDisposition("attachment; filename=\"foo.txt\"");
diff --git a/chrome/browser/download/download_target_determiner.cc b/chrome/browser/download/download_target_determiner.cc
index 0dba97c..76f7ad0 100644
--- a/chrome/browser/download/download_target_determiner.cc
+++ b/chrome/browser/download/download_target_determiner.cc
@@ -1255,10 +1255,16 @@
   // If the user has has been prompted or will be, assume that the user has
   // approved the download. A programmatic download is considered safe unless it
   // contains malware.
+  bool user_approved_path =
+      !download_->GetForcedFilePath().empty() &&
+      // Drag and drop download paths are not approved by the user. See
+      // https://crbug.com/1513639
+      download_->GetDownloadSource() != download::DownloadSource::DRAG_AND_DROP;
   if (HasPromptedForPath() ||
       confirmation_reason_ != DownloadConfirmationReason::NONE ||
-      !download_->GetForcedFilePath().empty())
+      user_approved_path) {
     return DownloadFileType::NOT_DANGEROUS;
+  }
 
   // User-initiated extension downloads from pref-whitelisted sources are not
   // considered dangerous.
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
index 712639d..49bd8ce2 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_apitest.cc
@@ -2,18 +2,18 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/extensions/extension_apitest.h"
-
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
+#include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/common/chrome_features.h"
 #include "components/version_info/version_info.h"
 #include "content/public/common/content_features.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/prerender_test_util.h"
+#include "extensions/browser/api/declarative_net_request/constants.h"
 #include "extensions/browser/api/declarative_net_request/utils.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/test/extension_test_message_listener.h"
@@ -100,6 +100,15 @@
   ASSERT_TRUE(RunExtensionTest("dynamic_rules")) << message_;
 }
 
+IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestLazyApiTest, RegexRuleMessage) {
+  // Ensure the error message for large RegEx rules is updated with the
+  // correct value for the memory limit.
+  std::string expected_amount = base::StringPrintf(
+      "%dKB", extensions::declarative_net_request::kRegexMaxMemKb);
+  EXPECT_THAT(extensions::declarative_net_request::kErrorRegexTooLarge,
+              testing::HasSubstr(expected_amount));
+}
+
 class DeclarativeNetRequestSafeRulesLazyApiTest
     : public DeclarativeNetRequestLazyApiTest {
  public:
diff --git a/chrome/browser/feedback/show_feedback_page.cc b/chrome/browser/feedback/show_feedback_page.cc
index 011e3fd9..58c35f9 100644
--- a/chrome/browser/feedback/show_feedback_page.cc
+++ b/chrome/browser/feedback/show_feedback_page.cc
@@ -276,7 +276,8 @@
                             const std::string& description_placeholder_text,
                             const std::string& category_tag,
                             const std::string& extra_diagnostics,
-                            base::Value::Dict autofill_metadata);
+                            base::Value::Dict autofill_metadata,
+                            base::Value::Dict ai_metadata);
 }  // namespace internal
 #endif
 
@@ -350,7 +351,8 @@
   // Send request to ash via crosapi mojo to show Feedback ui from ash.
   internal::ShowFeedbackPageLacros(
       page_url, source, description_template, description_placeholder_text,
-      category_tag, extra_diagnostics, std::move(autofill_metadata));
+      category_tag, extra_diagnostics, std::move(autofill_metadata),
+      std::move(ai_metadata));
 #else
   // Show feedback dialog using feedback extension API.
   RequestFeedbackFlow(page_url, profile, source, description_template,
diff --git a/chrome/browser/feedback/show_feedback_page_lacros.cc b/chrome/browser/feedback/show_feedback_page_lacros.cc
index 19835187..b609e07 100644
--- a/chrome/browser/feedback/show_feedback_page_lacros.cc
+++ b/chrome/browser/feedback/show_feedback_page_lacros.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chromeos/crosapi/mojom/feedback.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
+#include "chromeos/startup/browser_params_proxy.h"
 
 namespace chrome {
 namespace internal {
@@ -40,6 +41,8 @@
           kFeedbackSourceProfileErrorDialog;
     case kFeedbackSourceQuickOffice:
       return crosapi::mojom::LacrosFeedbackSource::kFeedbackSourceQuickOffice;
+    case kFeedbackSourceAI:
+      return crosapi::mojom::LacrosFeedbackSource::kFeedbackSourceAI;
     default:
       LOG(ERROR) << "ShowFeedbackPage is called by unknown Lacros source: "
                  << static_cast<int>(source);
@@ -55,7 +58,8 @@
     const std::string& description_placeholder_text,
     const std::string& category_tag,
     const std::string& extra_diagnostics,
-    base::Value::Dict autofill_metadata) {
+    base::Value::Dict autofill_metadata,
+    base::Value::Dict ai_metadata) {
   auto mojo_feedback = crosapi::mojom::FeedbackInfo::New();
   mojo_feedback->page_url = page_url;
   mojo_feedback->source = ToMojoLacrosFeedbackSource(source);
@@ -64,6 +68,7 @@
   mojo_feedback->category_tag = category_tag;
   mojo_feedback->extra_diagnostics = extra_diagnostics;
   mojo_feedback->autofill_metadata = base::Value(std::move(autofill_metadata));
+  mojo_feedback->ai_metadata = base::Value(std::move(ai_metadata));
   return mojo_feedback;
 }
 
@@ -77,12 +82,22 @@
                             const std::string& description_placeholder_text,
                             const std::string& category_tag,
                             const std::string& extra_diagnostics,
-                            base::Value::Dict autofill_metadata) {
+                            base::Value::Dict autofill_metadata,
+                            base::Value::Dict ai_metadata) {
+  if (source == kFeedbackSourceAI) {
+    auto capabilities = chromeos::BrowserParamsProxy::Get()->AshCapabilities();
+    if (!capabilities || !base::Contains(*capabilities, "crbug/1501057")) {
+      LOG(WARNING) << "Unsupported feedback source AI for ash.";
+      return;
+    }
+  }
+
   chromeos::LacrosService::Get()
       ->GetRemote<crosapi::mojom::Feedback>()
       ->ShowFeedbackPage(ToMojoFeedbackInfo(
           page_url, source, description_template, description_placeholder_text,
-          category_tag, extra_diagnostics, std::move(autofill_metadata)));
+          category_tag, extra_diagnostics, std::move(autofill_metadata),
+          std::move(ai_metadata)));
 }
 
 }  // namespace internal
diff --git a/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc b/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
index ef42adc..8942c19 100644
--- a/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
+++ b/chrome/browser/feedback/show_feedback_page_lacros_browsertest.cc
@@ -6,6 +6,7 @@
 #include "base/version.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chromeos/startup/browser_params_proxy.h"
 #include "components/version_info/version_info.h"
 #include "content/public/test/browser_test.h"
 
@@ -124,3 +125,23 @@
   ShowFeedbackPageWithFeedbackSource(chrome::kFeedbackSourceQuickOffice);
   histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
 }
+
+IN_PROC_BROWSER_TEST_F(ShowFeedbackPageBrowserTest, ShowFeedbackPageFromAI) {
+  base::HistogramTester histogram_tester;
+  std::string unused;
+  auto capabilities = chromeos::BrowserParamsProxy::Get()->AshCapabilities();
+  if (!capabilities || !base::Contains(*capabilities, "crbug/1501057")) {
+    GTEST_SKIP() << "Unsupported feedback source AI for ash.";
+  }
+
+  // AI flow uses the Chrome feedback dialog instead so no new ash window will
+  // be created.
+  chrome::ShowFeedbackPage(browser(), chrome::kFeedbackSourceAI,
+                           /*description_template=*/unused,
+                           /*description_placeholder_text=*/unused,
+                           /*category_tag=*/unused,
+                           /*extra_diagnostics=*/unused,
+                           /*autofill_metadata=*/base::Value::Dict(),
+                           /*ai_metadata=*/base::Value::Dict());
+  histogram_tester.ExpectTotalCount("Feedback.RequestSource", 1);
+}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 13c867c..b0519170 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -5174,6 +5174,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "left-hand-side-activity-indicators",
+    "owners": [ "elklm@chromium.org", "//components/permissions/PERMISSIONS_OWNERS" ],
+    "expiry_milestone": 135
+  },
+  {
     "name": "legacy-tech-report-enable-cookie-issue-reports",
     "owners": ["sandormajor@google.com", "zmin@chromium.org", "parastoog@chromium.org"],
     "expiry_milestone": 125
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index b546553..bb59f289 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2789,6 +2789,11 @@
 const char kImprovedSemanticsActivityIndicatorsDescription[] =
     "Enables experimental improved semantics indicators in the location bar.";
 
+const char kLeftHandSideActivityIndicatorsName[] =
+    "Left-hand side activity indicators";
+const char kLeftHandSideActivityIndicatorsDescription[] =
+    "Moves activity indicators to the left-hand side of location bar.";
+
 const char kPermissionPredictionsName[] = "Permission Predictions";
 const char kPermissionPredictionsDescription[] =
     "Use the Permission Predictions Service to surface permission requests "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 652da05e..0ba824b 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1605,6 +1605,9 @@
 extern const char kImprovedSemanticsActivityIndicatorsName[];
 extern const char kImprovedSemanticsActivityIndicatorsDescription[];
 
+extern const char kLeftHandSideActivityIndicatorsName[];
+extern const char kLeftHandSideActivityIndicatorsDescription[];
+
 extern const char kPermissionPredictionsName[];
 extern const char kPermissionPredictionsDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 6a1a3e5..6aba36b9 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -626,7 +626,7 @@
 
 BASE_FEATURE(kMultiInstanceApplicationStatusCleanup,
              "MultiInstanceApplicationStatusCleanup",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 BASE_FEATURE(kNewTabSearchEngineUrlAndroid,
              "NewTabSearchEngineUrlAndroid",
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 2f5b4a808..ba822628 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -513,7 +513,7 @@
             new CachedFlag(HIDE_TAB_ON_TAB_SWITCHER, true);
     public static final CachedFlag sMagicStackAndroid = new CachedFlag(MAGIC_STACK_ANDROID, false);
     public static final CachedFlag sMultiInstanceApplicationStatusCleanup =
-            new CachedFlag(MUlTI_INSTANCE_APPLICATION_STATUS_CLEANUP, true);
+            new CachedFlag(MUlTI_INSTANCE_APPLICATION_STATUS_CLEANUP, false);
     public static final CachedFlag sNewTabSearchEngineUrlAndroid =
             new CachedFlag(NEW_TAB_SEARCH_ENGINE_URL_ANDROID, false);
     public static final CachedFlag sPriceChangeModule = new CachedFlag(PRICE_CHANGE_MODULE, false);
diff --git a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
index 2e64a4b..6664b142 100644
--- a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
+++ b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
@@ -69,6 +69,11 @@
 using guest_view::TestGuestViewManager;
 using guest_view::TestGuestViewManagerFactory;
 
+namespace {
+// The value of the data is "content to read\n".
+const char kDataUrlCsv[] = "data:text/csv;base64,Y29udGVudCB0byByZWFkCg==";
+}  // namespace
+
 class ChromeMimeHandlerViewTest : public extensions::ExtensionApiTest {
  public:
   ChromeMimeHandlerViewTest() = default;
@@ -400,7 +405,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewTest, DataUrl) {
-  const char* kDataUrlCsv = "data:text/csv;base64,Y29udGVudCB0byByZWFkCg==";
   RunTestWithUrl(GURL(kDataUrlCsv));
 }
 
@@ -654,12 +658,11 @@
 
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewTest,
                        GuestDevToolsReloadsEmbedder) {
-  GURL data_url("data:application/pdf,foo");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), data_url));
+  GURL data_url(kDataUrlCsv);
+  RunTestWithUrl(data_url);
   auto* embedder_web_contents =
       browser()->tab_strip_model()->GetWebContentsAt(0);
   auto* guest_view = GetGuestViewManager()->WaitForSingleGuestViewCreated();
-  ASSERT_TRUE(guest_view);
   EXPECT_NE(embedder_web_contents->GetPrimaryMainFrame(),
             guest_view->GetGuestMainFrame());
   TestMimeHandlerViewGuest::WaitForGuestLoadStartThenStop(guest_view);
@@ -686,10 +689,10 @@
 IN_PROC_BROWSER_TEST_F(ChromeMimeHandlerViewTest,
                        MimeHandlerViewInDisplayNoneFrameForGoogleApps) {
   GURL data_url(
-      "data:text/html, <iframe src='data:application/pdf,foo' "
-      "style='display:none'></iframe>,foo2");
-  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), data_url));
-  ASSERT_TRUE(GetGuestViewManager()->WaitForSingleGuestViewCreated());
+      base::StringPrintf("data:text/html, <iframe src='%s' "
+                         "style='display:none'></iframe>,foo2",
+                         kDataUrlCsv));
+  RunTestWithUrl(data_url);
 }
 
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
diff --git a/chrome/browser/hub/BUILD.gn b/chrome/browser/hub/BUILD.gn
index a1ee455..61b217d 100644
--- a/chrome/browser/hub/BUILD.gn
+++ b/chrome/browser/hub/BUILD.gn
@@ -28,6 +28,7 @@
     "android/java/src/org/chromium/chrome/browser/hub/HubManager.java",
     "android/java/src/org/chromium/chrome/browser/hub/LoadHint.java",
     "android/java/src/org/chromium/chrome/browser/hub/Pane.java",
+    "android/java/src/org/chromium/chrome/browser/hub/PaneHubController.java",
     "android/java/src/org/chromium/chrome/browser/hub/PaneId.java",
     "android/java/src/org/chromium/chrome/browser/hub/PaneListBuilder.java",
     "android/java/src/org/chromium/chrome/browser/hub/PaneLookup.java",
diff --git a/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/Pane.java b/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/Pane.java
index 73e4062..78fe0b0 100644
--- a/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/Pane.java
+++ b/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/Pane.java
@@ -7,6 +7,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
@@ -29,6 +30,14 @@
     void destroy();
 
     /**
+     * Sets an interface for controlling certain aspects of the Hub while focused.
+     *
+     * @param paneHubController An interface that can be used to control the hub, may be null when
+     *     not focused. If null is set do not keep a reference to the old controller.
+     */
+    void setPaneHubController(@Nullable PaneHubController paneHubController);
+
+    /**
      * Notifies of a change to the Hub's or the pane's lifecycle. See {@link LoadHint} for possible
      * values and what the pane could or should do in response to a notification.
      *
diff --git a/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/PaneHubController.java b/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/PaneHubController.java
new file mode 100644
index 0000000..de8d6ab1
--- /dev/null
+++ b/chrome/browser/hub/android/java/src/org/chromium/chrome/browser/hub/PaneHubController.java
@@ -0,0 +1,29 @@
+// Copyright 2024 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.hub;
+
+import org.chromium.chrome.browser.tab.Tab;
+
+/**
+ * Interface for controlling the Hub from a {@link Pane}. This is only available to the focused
+ * pane.
+ */
+public interface PaneHubController {
+    /**
+     * Sets a tab as active and hides the Hub. A tab must be selected if the browser is
+     * transitioning to an active tab. Use {@link Tab.INVALID_TAB_ID} if a tab has already been
+     * selected and doing so would repeat work.
+     *
+     * @param tabId The ID of the tab to select or {@link Tab.INVALID_TAB_ID}.
+     */
+    public void selectTabAndHideHub(int tabId);
+
+    /**
+     * Focuses a pane taking focus away from the current pane.
+     *
+     * @param paneId The ID of the pane to focus.
+     */
+    public void focusPane(@PaneId int paneId);
+}
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
index 60b240a5..a8b0ba0 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinator.java
@@ -10,6 +10,7 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
@@ -21,17 +22,17 @@
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.BackPressResult;
 
 /** Root coordinator of the Hub. */
-public class HubCoordinator implements BackPressHandler {
+public class HubCoordinator implements PaneHubController, BackPressHandler {
     private static final Integer START_SURFACE_LAYOUT_TYPE =
             Integer.valueOf(LayoutType.START_SURFACE);
 
     private final @NonNull FrameLayout mContainerView;
     private final @NonNull View mMainHubParent;
+    private final @NonNull PaneManager mPaneManager;
     private final @NonNull HubToolbarCoordinator mHubToolbarCoordinator;
     private final @NonNull HubPaneHostCoordinator mHubPaneHostCoordinator;
     private final @NonNull HubLayoutController mHubLayoutController;
     private final @NonNull ObservableSupplierImpl<Boolean> mHandleBackPressSupplier;
-    private final @NonNull ObservableSupplier<Pane> mFocusedPaneSupplier;
 
     /**
      * Generic callback that invokes {@link #updateHandleBackPressSupplier()}. This can be cast to
@@ -40,7 +41,7 @@
     private final @NonNull Callback<Object> mBackPressStateChangeCallback;
 
     /**
-     * Warning: {@link mFocusedPaneSupplier#get()} may return null if no pane is focused or {@link
+     * Warning: {@link #getFocusedPane()} may return null if no pane is focused or {@link
      * Pane#getHandleBackPressChangedSupplier()} contains null.
      */
     private final @NonNull TransitiveObservableSupplier<Pane, Boolean>
@@ -64,10 +65,11 @@
             @NonNull ObservableSupplier<Tab> currentTabSupplier) {
         Context context = containerView.getContext();
         mBackPressStateChangeCallback = (ignored) -> updateHandleBackPressSupplier();
-        mFocusedPaneSupplier = paneManager.getFocusedPaneSupplier();
+        mPaneManager = paneManager;
         mFocusedPaneHandleBackPressSupplier =
                 new TransitiveObservableSupplier<>(
-                        mFocusedPaneSupplier, p -> p.getHandleBackPressChangedSupplier());
+                        paneManager.getFocusedPaneSupplier(),
+                        p -> p.getHandleBackPressChangedSupplier());
         mFocusedPaneHandleBackPressSupplier.addObserver(
                 castCallback(mBackPressStateChangeCallback));
 
@@ -122,7 +124,7 @@
     @Override
     public @BackPressResult int handleBackPress() {
         if (Boolean.TRUE.equals(mFocusedPaneHandleBackPressSupplier.get())
-                && mFocusedPaneSupplier.get().handleBackPress() == BackPressResult.SUCCESS) {
+                && getFocusedPane().handleBackPress() == BackPressResult.SUCCESS) {
             return BackPressResult.SUCCESS;
         }
 
@@ -151,6 +153,20 @@
         return mHandleBackPressSupplier;
     }
 
+    @Override
+    public void selectTabAndHideHub(int tabId) {
+        mHubLayoutController.selectTabAndHideHubLayout(tabId);
+    }
+
+    @Override
+    public void focusPane(@PaneId int paneId) {
+        mPaneManager.focusPane(paneId);
+    }
+
+    private @Nullable Pane getFocusedPane() {
+        return mPaneManager.getFocusedPaneSupplier().get();
+    }
+
     private boolean startSurfaceHandlesBackpress() {
         Tab currentTab = mCurrentTabSupplier.get();
         boolean isIncognito = currentTab != null ? currentTab.isIncognito() : false;
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinatorUnitTest.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinatorUnitTest.java
index 1accc851..ebdf896 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinatorUnitTest.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubCoordinatorUnitTest.java
@@ -11,6 +11,8 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -99,7 +101,7 @@
                         .registerPane(
                                 PaneId.INCOGNITO_TAB_SWITCHER,
                                 LazyOneshotSupplier.fromValue(mIncognitoTabSwitcherPane));
-        mPaneManager = new PaneManagerImpl(builder, mHubVisibilitySupplier);
+        mPaneManager = spy(new PaneManagerImpl(builder, mHubVisibilitySupplier));
 
         assertTrue(mPaneManager.focusPane(PaneId.TAB_SWITCHER));
         assertEquals(mTabSwitcherPane, mPaneManager.getFocusedPaneSupplier().get());
@@ -280,4 +282,20 @@
         assertEquals(BackPressResult.SUCCESS, mHubCoordinator.handleBackPress());
         verify(mHubLayoutController).selectTabAndHideHubLayout(eq(TAB_ID));
     }
+
+    @Test
+    @SmallTest
+    public void testFocusPane() {
+        reset(mPaneManager);
+        mHubCoordinator.focusPane(PaneId.TAB_SWITCHER);
+        verify(mPaneManager).focusPane(PaneId.TAB_SWITCHER);
+    }
+
+    @Test
+    @SmallTest
+    public void testSelectTabAndHideHub() {
+        int tabId = 5;
+        mHubCoordinator.selectTabAndHideHub(tabId);
+        verify(mHubLayoutController).selectTabAndHideHubLayout(tabId);
+    }
 }
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImpl.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImpl.java
index 5a4e946..b7a02e6 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImpl.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImpl.java
@@ -7,7 +7,9 @@
 import android.content.Context;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
+import org.chromium.base.ValueChangedCallback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.chrome.browser.back_press.BackPressManager;
@@ -22,9 +24,11 @@
  * created and torn down as needed when {@link HubLayout} visibility changes.
  */
 public class HubManagerImpl implements HubManager, HubController {
-    private final @NonNull Context mContext;
+    private final ValueChangedCallback<Pane> mOnFocusedPaneChanged =
+            new ValueChangedCallback<>(this::onFocusedPaneChanged);
     private final @NonNull ObservableSupplierImpl<Boolean> mHubVisibilitySupplier =
             new ObservableSupplierImpl<>();
+    private final @NonNull Context mContext;
     private final @NonNull PaneManagerImpl mPaneManager;
     private final @NonNull HubContainerView mHubContainerView;
     private final @NonNull BackPressManager mBackPressManager;
@@ -48,10 +52,13 @@
 
         // TODO(crbug/1487315): Consider making this a xml file so the entire core UI is inflated.
         mHubContainerView = new HubContainerView(mContext);
+
+        mPaneManager.getFocusedPaneSupplier().addObserver(mOnFocusedPaneChanged);
     }
 
     @Override
     public void destroy() {
+        mPaneManager.getFocusedPaneSupplier().removeObserver(mOnFocusedPaneChanged);
         mPaneManager.destroy();
         destroyHubCoordinator();
     }
@@ -117,10 +124,15 @@
                 new HubCoordinator(
                         mHubContainerView, mPaneManager, mHubLayoutController, mTabSupplier);
         mBackPressManager.addHandler(mHubCoordinator, BackPressHandler.Type.HUB);
+        Pane pane = mPaneManager.getFocusedPaneSupplier().get();
+        if (pane != null) pane.setPaneHubController(mHubCoordinator);
     }
 
     private void destroyHubCoordinator() {
         if (mHubCoordinator != null) {
+            Pane pane = mPaneManager.getFocusedPaneSupplier().get();
+            if (pane != null) pane.setPaneHubController(null);
+
             mBackPressManager.removeHandler(mHubCoordinator);
             mHubCoordinator.destroy();
             mHubCoordinator = null;
@@ -130,4 +142,9 @@
     HubCoordinator getHubCoordinatorForTesting() {
         return mHubCoordinator;
     }
+
+    private void onFocusedPaneChanged(@Nullable Pane newPane, @Nullable Pane oldPane) {
+        if (oldPane != null) oldPane.setPaneHubController(null);
+        if (newPane != null) newPane.setPaneHubController(mHubCoordinator);
+    }
 }
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImplUnitTest.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImplUnitTest.java
index 0ae03ad..57cde3d3 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImplUnitTest.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/HubManagerImplUnitTest.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -51,15 +52,31 @@
     @Mock private BackPressManager mBackPressManager;
     @Mock private Tab mTab;
     @Mock private Pane mTabSwitcherPane;
+    @Mock private Pane mIncognitoTabSwitcherPane;
     @Mock private HubLayoutController mHubLayoutController;
     @Mock private ObservableSupplier<Integer> mPreviousLayoutTypeSupplier;
 
+    private final ObservableSupplierImpl<Tab> mTabSupplier = new ObservableSupplierImpl<>();
+    private final ObservableSupplierImpl<DisplayButtonData> mReferenceButtonDataSupplier =
+            new ObservableSupplierImpl<>();
+    private final ObservableSupplierImpl<FullButtonData> mActionButtonDataSupplier =
+            new ObservableSupplierImpl<>();
+
     private Activity mActivity;
     private FrameLayout mRootView;
-    private ObservableSupplierImpl<Tab> mTabSupplier = new ObservableSupplierImpl<>();
 
     @Before
     public void setUp() {
+        when(mTabSwitcherPane.getPaneId()).thenReturn(PaneId.TAB_SWITCHER);
+        when(mTabSwitcherPane.getReferenceButtonDataSupplier())
+                .thenReturn(mReferenceButtonDataSupplier);
+        when(mTabSwitcherPane.getActionButtonDataSupplier()).thenReturn(mActionButtonDataSupplier);
+        when(mIncognitoTabSwitcherPane.getPaneId()).thenReturn(PaneId.INCOGNITO_TAB_SWITCHER);
+        when(mIncognitoTabSwitcherPane.getReferenceButtonDataSupplier())
+                .thenReturn(mReferenceButtonDataSupplier);
+        when(mIncognitoTabSwitcherPane.getActionButtonDataSupplier())
+                .thenReturn(mActionButtonDataSupplier);
+
         when(mHubLayoutController.getPreviousLayoutTypeSupplier())
                 .thenReturn(mPreviousLayoutTypeSupplier);
         when(mTab.getId()).thenReturn(TAB_ID);
@@ -81,7 +98,10 @@
                 new PaneListBuilder(new DefaultPaneOrderController())
                         .registerPane(
                                 PaneId.TAB_SWITCHER,
-                                LazyOneshotSupplier.fromValue(mTabSwitcherPane));
+                                LazyOneshotSupplier.fromValue(mTabSwitcherPane))
+                        .registerPane(
+                                PaneId.INCOGNITO_TAB_SWITCHER,
+                                LazyOneshotSupplier.fromValue(mIncognitoTabSwitcherPane));
         HubManager hubManager =
                 HubManagerFactory.createHubManager(
                         mActivity, builder, mBackPressManager, mTabSupplier);
@@ -96,9 +116,18 @@
     @Test
     @SmallTest
     public void testHubController() {
-        PaneListBuilder builder = new PaneListBuilder(new DefaultPaneOrderController());
+        PaneListBuilder builder =
+                new PaneListBuilder(new DefaultPaneOrderController())
+                        .registerPane(
+                                PaneId.TAB_SWITCHER,
+                                LazyOneshotSupplier.fromValue(mTabSwitcherPane))
+                        .registerPane(
+                                PaneId.INCOGNITO_TAB_SWITCHER,
+                                LazyOneshotSupplier.fromValue(mIncognitoTabSwitcherPane));
         HubManagerImpl hubManager =
                 new HubManagerImpl(mActivity, builder, mBackPressManager, mTabSupplier);
+        hubManager.getPaneManager().focusPane(PaneId.TAB_SWITCHER);
+        verify(mTabSwitcherPane).setPaneHubController(null);
         HubController hubController = hubManager.getHubController();
         hubController.setHubLayoutController(mHubLayoutController);
         assertNull(hubManager.getHubCoordinatorForTesting());
@@ -107,6 +136,7 @@
         HubCoordinator coordinator = hubManager.getHubCoordinatorForTesting();
         assertNotNull(coordinator);
         verify(mBackPressManager).addHandler(eq(coordinator), eq(BackPressHandler.Type.HUB));
+        verify(mTabSwitcherPane).setPaneHubController(coordinator);
 
         View containerView = hubController.getContainerView();
         assertNotNull(containerView);
@@ -115,9 +145,14 @@
         mRootView.addView(containerView);
         assertEquals(mRootView, containerView.getParent());
 
+        hubManager.getPaneManager().focusPane(PaneId.INCOGNITO_TAB_SWITCHER);
+        verify(mTabSwitcherPane, times(2)).setPaneHubController(null);
+        verify(mIncognitoTabSwitcherPane).setPaneHubController(coordinator);
+
         hubController.onHubLayoutDoneHiding();
         assertNull(hubManager.getHubCoordinatorForTesting());
         verify(mBackPressManager).removeHandler(eq(coordinator));
+        verify(mIncognitoTabSwitcherPane).setPaneHubController(null);
 
         // Container is still attached and will be removed separately.
         assertEquals(mRootView, containerView.getParent());
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
index 730e74d..89d70d0 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.cc
@@ -1168,12 +1168,6 @@
   }
 }
 
-void ChromeBrowserMainExtraPartsMetrics::PostMainMessageLoopRun() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  profile_manager_observation_.Reset();
-#endif
-}
-
 void ChromeBrowserMainExtraPartsMetrics::RegisterPrefs(
     PrefRegistrySimple* registry) {
   registry->RegisterIntegerPref(kEnableBenchmarkingPrefId,
@@ -1210,16 +1204,23 @@
 
 void ChromeBrowserMainExtraPartsMetrics::
     HandleEnableBenchmarkingCountdownAsync() {
-  // On ChromeOS we must wait until post-login to be able to accurately assess
-  // whether the enable-benchmarking flag has been enabled. This logic assumes
-  // that it always runs pre-login.
+  Profile* profile = nullptr;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-  profile_manager_observation_.Observe(g_browser_process->profile_manager());
-#else
-  about_flags::GetStorage(/*profile=*/nullptr,
+  // This logic is subtle. There are two ways for ash-chrome PostBrowserStart to
+  // be called on ChromeOS. The first is when the device first shows the login
+  // screen. In this case the profile is the login profile. The second is after
+  // the user logs in. If any flags have been changed from the login profile's
+  // flags, then all of ash is restarted. We only care about invoking this logic
+  // in the second case. Thus we check if IsUserLoggedIn() to guard the logic.
+  if (!user_manager::UserManager::IsInitialized() ||
+      !user_manager::UserManager::Get()->IsUserLoggedIn()) {
+    return;
+  }
+  profile = g_browser_process->profile_manager()->GetPrimaryUserProfile();
+#endif
+  about_flags::GetStorage(profile,
                           base::BindOnce(&HandleEnableBenchmarkingCountdown,
                                          g_browser_process->local_state()));
-#endif
 }
 
 void ChromeBrowserMainExtraPartsMetrics::OnDisplayAdded(
@@ -1250,23 +1251,6 @@
   }
 }
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-void ChromeBrowserMainExtraPartsMetrics::OnProfileAdded(Profile* profile) {
-  // This may be called with the login profile which is a side effect when
-  // ash-chrome restarts during login. We only want to trigger the
-  // HandleEnableBenchmarkingCountdown logic for the primary profile.
-
-  if (!user_manager::UserManager::Get()->IsPrimaryUser(
-          ash::BrowserContextHelper::Get()->GetUserByBrowserContext(profile))) {
-    return;
-  }
-
-  about_flags::GetStorage(profile,
-                          base::BindOnce(&HandleEnableBenchmarkingCountdown,
-                                         g_browser_process->local_state()));
-}
-#endif
-
 namespace chrome {
 
 void AddMetricsExtraParts(ChromeBrowserMainParts* main_parts) {
diff --git a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.h b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.h
index 6f995f6..a76b426e 100644
--- a/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.h
+++ b/chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.h
@@ -13,16 +13,11 @@
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
-#include "chrome/browser/profiles/profile_manager_observer.h"
 #include "components/flags_ui/flags_state.h"
 #include "components/flags_ui/flags_storage.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/display/display_observer.h"
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "chrome/browser/profiles/profile_manager.h"
-#endif
-
 class ChromeBrowserMainParts;
 class PrefRegistrySimple;
 class PrefService;
@@ -47,8 +42,7 @@
 }
 
 class ChromeBrowserMainExtraPartsMetrics : public ChromeBrowserMainExtraParts,
-                                           public display::DisplayObserver,
-                                           public ProfileManagerObserver {
+                                           public display::DisplayObserver {
  public:
   ChromeBrowserMainExtraPartsMetrics();
 
@@ -66,7 +60,6 @@
   void PreBrowserStart() override;
   void PostBrowserStart() override;
   void PreMainMessageLoopRun() override;
-  void PostMainMessageLoopRun() override;
 
   // Registers local state prefs used by this class.
   static void RegisterPrefs(PrefRegistrySimple* registry);
@@ -105,11 +98,6 @@
   // If the number of displays has changed, emit a UMA metric.
   void EmitDisplaysChangedMetric();
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  // On ChromeOS, we must wait for post login to have a valid browser Profile*.
-  void OnProfileAdded(Profile* profile) override;
-#endif
-
   // A cached value for the number of displays.
   int display_count_;
 
@@ -130,11 +118,6 @@
   // Reports pressure metrics.
   std::unique_ptr<PressureMetricsReporter> pressure_metrics_reporter_;
 #endif  // BUILDFLAG(IS_LINUX)
-
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  base::ScopedObservation<ProfileManager, ChromeBrowserMainExtraPartsMetrics>
-      profile_manager_observation_{this};
-#endif
 };
 
 #endif  // CHROME_BROWSER_METRICS_CHROME_BROWSER_MAIN_EXTRA_PARTS_METRICS_H_
diff --git a/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.cc b/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.cc
index 9f2195a..856fbd6 100644
--- a/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.cc
+++ b/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.cc
@@ -44,10 +44,15 @@
   audio_focus_remote->AddObserver(
       audio_focus_observer_receiver_.BindNewPipeAndPassRemote());
 
-  // Connect to receive media session updates.
-  content::MediaSession::Get(web_contents)
-      ->AddObserver(
-          media_session_observer_receiver_.BindNewPipeAndPassRemote());
+  // Connect to receive media session updates if the media session already
+  // exists. If it does not, then we'll become an observer in
+  // `MediaSessionCreated()`.
+  content::MediaSession* media_session =
+      content::MediaSession::GetIfExists(web_contents);
+  if (media_session) {
+    media_session->AddObserver(
+        media_session_observer_receiver_.BindNewPipeAndPassRemote());
+  }
 }
 
 AutoPictureInPictureTabHelper::~AutoPictureInPictureTabHelper() = default;
@@ -86,6 +91,13 @@
   }
 }
 
+void AutoPictureInPictureTabHelper::MediaSessionCreated(
+    content::MediaSession* media_session) {
+  // Connect to receive media session updates.
+  media_session->AddObserver(
+      media_session_observer_receiver_.BindNewPipeAndPassRemote());
+}
+
 void AutoPictureInPictureTabHelper::OnTabActivatedChanged(
     bool is_tab_activated) {
   is_tab_activated_ = is_tab_activated;
diff --git a/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.h b/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.h
index fc6a0a1..bee3598d 100644
--- a/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.h
+++ b/chrome/browser/picture_in_picture/auto_picture_in_picture_tab_helper.h
@@ -47,6 +47,7 @@
   // content::WebContentsObserver:
   void PrimaryPageChanged(content::Page& page) override;
   void MediaPictureInPictureChanged(bool is_in_picture_in_picture) override;
+  void MediaSessionCreated(content::MediaSession* media_session) override;
 
   // Called by `tab_strip_observer_helper_` when the tab changes between
   // activated and unactivated.
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 277034d..1ae5c2a 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -791,6 +791,11 @@
   { key::kForceGoogleSafeSearch,
     policy_prefs::kForceGoogleSafeSearch,
     base::Value::Type::BOOLEAN },
+#if BUILDFLAG(IS_CHROMEOS)
+  { key::kEssentialSearchEnabled,
+    prefs::kEssentialSearchEnabled,
+    base::Value::Type::BOOLEAN },
+#endif  // BUILDFLAG(IS_CHROMEOS)
   { key::kForceYouTubeRestrict,
     policy::policy_prefs::kForceYouTubeRestrict,
     base::Value::Type::INTEGER },
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 7a6e93f..58ca863 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1854,6 +1854,7 @@
   registry->RegisterBooleanPref(prefs::kDeskAPIDeskSaveAndShareEnabled, false);
   registry->RegisterListPref(prefs::kDeskAPIThirdPartyAllowlist);
   registry->RegisterBooleanPref(prefs::kInsightsExtensionEnabled, false);
+  registry->RegisterBooleanPref(prefs::kEssentialSearchEnabled, false);
   // By default showing Sync Consent is set to true. It can changed by policy.
   registry->RegisterBooleanPref(prefs::kEnableSyncConsent, true);
   registry->RegisterListPref(
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index c2ba0da..908b53d9 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -210,6 +210,7 @@
 #include "components/captive_portal/core/buildflags.h"
 #include "components/commerce/core/proto/commerce_subscription_db_content.pb.h"
 #include "components/commerce/core/proto/persisted_state_db_content.pb.h"
+#include "components/enterprise/buildflags/buildflags.h"
 #include "components/enterprise/content/clipboard_restriction_service.h"
 #include "components/media_effects/media_effects_service_factory.h"
 #include "components/offline_pages/buildflags/buildflags.h"
@@ -506,6 +507,10 @@
 
 #endif  // BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
 
+#if BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
+#include "chrome/browser/enterprise/data_controls/rules_service.h"
+#endif
+
 namespace chrome {
 
 void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
@@ -738,6 +743,9 @@
     CrosAppsKeyEventHandlerFactory::GetInstance();
   }
 #endif
+#if BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
+  data_controls::RulesServiceFactory::GetInstance();
+#endif
   data_sharing::DataSharingServiceFactory::GetInstance();
 #if !BUILDFLAG(IS_ANDROID)
   DevToolsAndroidBridge::Factory::GetInstance();
diff --git a/chrome/browser/profiles/profile_keyed_service_browsertest.cc b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
index 7154c0f..2a89dba 100644
--- a/chrome/browser/profiles/profile_keyed_service_browsertest.cc
+++ b/chrome/browser/profiles/profile_keyed_service_browsertest.cc
@@ -580,6 +580,7 @@
     "AutocompleteControllerEmitter",
     "AutofillInternalsService",
     "CanMakePaymentQuery",
+    "DataControlsRulesService",
     "LocalPresentationManager",
     "OmniboxInputWatcher",
     "OmniboxSuggestionsWatcher",
@@ -623,6 +624,7 @@
     // default, however their creation is still possible.
     "AutocompleteControllerEmitter",
     "CanMakePaymentQuery",
+    "DataControlsRulesService",
     "OmniboxInputWatcher",
     "OmniboxSuggestionsWatcher",
     "PolicyBlocklist",
diff --git a/chrome/browser/push_notification/BUILD.gn b/chrome/browser/push_notification/BUILD.gn
index 1e276149..db687e4c 100644
--- a/chrome/browser/push_notification/BUILD.gn
+++ b/chrome/browser/push_notification/BUILD.gn
@@ -6,8 +6,6 @@
   sources = [
     "prefs/push_notification_prefs.cc",
     "prefs/push_notification_prefs.h",
-    "push_notification_client_manager_desktop_impl.cc",
-    "push_notification_client_manager_desktop_impl.h",
     "push_notification_service_desktop_impl.cc",
     "push_notification_service_desktop_impl.h",
   ]
diff --git a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.cc b/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.cc
deleted file mode 100644
index 641a35a..0000000
--- a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.cc
+++ /dev/null
@@ -1,20 +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 "chrome/browser/push_notification/push_notification_client_manager_desktop_impl.h"
-
-namespace push_notification {
-PushNotificationClientManagerDesktopImpl::
-    PushNotificationClientManagerDesktopImpl() = default;
-PushNotificationClientManagerDesktopImpl::
-    ~PushNotificationClientManagerDesktopImpl() = default;
-
-void PushNotificationClientManagerDesktopImpl::
-    NotifyPushNotificationClientOfMessage(PushNotificationMessage message) {
-  // TODO(b/287340843): Parse the message and
-  // extract the `PushNotificationClientId` to pass the message to the correct
-  // `PushNotificationClient`.
-}
-
-}  // namespace push_notification
diff --git a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.h b/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.h
deleted file mode 100644
index e9a64f71..0000000
--- a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl.h
+++ /dev/null
@@ -1,29 +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 CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_DESKTOP_IMPL_H_
-#define CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_DESKTOP_IMPL_H_
-
-#include "components/push_notification/push_notification_client_manager.h"
-
-namespace push_notification {
-
-class PushNotificationClientManagerDesktopImpl
-    : public PushNotificationClientManager {
- public:
-  PushNotificationClientManagerDesktopImpl();
-  PushNotificationClientManagerDesktopImpl(
-      const PushNotificationClientManagerDesktopImpl&) = delete;
-  PushNotificationClientManagerDesktopImpl& operator=(
-      const PushNotificationClientManagerDesktopImpl&) = delete;
-  ~PushNotificationClientManagerDesktopImpl() override;
-
-  // PushNotificationClientManager:
-  void NotifyPushNotificationClientOfMessage(
-      PushNotificationMessage message) override;
-};
-
-}  // namespace push_notification
-
-#endif  // CHROME_BROWSER_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_DESKTOP_IMPL_H_
diff --git a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl_unittest.cc b/chrome/browser/push_notification/push_notification_client_manager_desktop_impl_unittest.cc
deleted file mode 100644
index 56523b6..0000000
--- a/chrome/browser/push_notification/push_notification_client_manager_desktop_impl_unittest.cc
+++ /dev/null
@@ -1,34 +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 "chrome/browser/push_notification/push_notification_client_manager_desktop_impl.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-#include <memory>
-
-namespace push_notification {
-
-class PushNotificationClientManagerDesktopImplTest : public testing::Test {
- public:
-  PushNotificationClientManagerDesktopImplTest() = default;
-  ~PushNotificationClientManagerDesktopImplTest() override = default;
-
-  // testing::Test:
-  void SetUp() override {
-    push_notification_client_manager_ =
-        std::make_unique<PushNotificationClientManagerDesktopImpl>();
-  }
-
-  std::unique_ptr<PushNotificationClientManagerDesktopImpl>
-      push_notification_client_manager_;
-};
-
-TEST_F(PushNotificationClientManagerDesktopImplTest, AddClient) {
-  EXPECT_TRUE(push_notification_client_manager_);
-  // TODO(b/306398998): Test adding, removing and passing a message to a client
-  // when that functionality is implemented.
-}
-
-}  // namespace push_notification
diff --git a/chrome/browser/push_notification/push_notification_service_desktop_impl.cc b/chrome/browser/push_notification/push_notification_service_desktop_impl.cc
index 98aeeab..e265b56a 100644
--- a/chrome/browser/push_notification/push_notification_service_desktop_impl.cc
+++ b/chrome/browser/push_notification/push_notification_service_desktop_impl.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/push_notification/push_notification_service_desktop_impl.h"
+
 #include "base/check.h"
 #include "components/prefs/pref_service.h"
 
@@ -16,6 +17,10 @@
 PushNotificationServiceDesktopImpl::~PushNotificationServiceDesktopImpl() =
     default;
 
-void PushNotificationServiceDesktopImpl::Shutdown() {}
+void PushNotificationServiceDesktopImpl::Shutdown() {
+  // TODO(b/306398998): Once fetching GCM token is implemented, reset the token
+  // here.
+  client_manager_.reset();
+}
 
 }  // namespace push_notification
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
index b6a4706f..1580eae 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -733,6 +733,7 @@
         promise.then(
                 playback -> {
                     Log.d(TAG, "Voice preview playback created.");
+                    ReadAloudMetrics.recordVoicePreviewed(voice.getVoiceId());
                     mVoicePreviewPlayback = playback;
                     playback.addListener(mVoicePreviewPlaybackListener);
                     mVoicePreviewPlayback.play();
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
index 62563aa3d..f7bcbd7 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
@@ -921,6 +921,28 @@
     }
 
     @Test
+    public void testPreviewVoice_metric() {
+        final String histogramName = ReadAloudMetrics.VOICE_PREVIEWED;
+
+        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "abc", true);
+
+        // Play tab.
+        requestAndStartPlayback();
+
+        reset(mPlaybackHooks);
+        // Preview a voice.
+        var voice = new PlaybackVoice("en", "abc", "");
+        doReturn(List.of(voice)).when(mPlaybackHooks).getVoicesFor(anyString());
+        doReturn(List.of(voice)).when(mPlaybackHooks).getPlaybackVoiceList(any());
+        mController.previewVoice(voice);
+        verify(mPlaybackHooks).createPlayback(any(), mPlaybackCallbackCaptor.capture());
+        Playback previewPlayback = Mockito.mock(Playback.class);
+        onPlaybackSuccess(previewPlayback);
+
+        histogram.assertExpected();
+    }
+
+    @Test
     public void testRestorePlaybackState_whileLoading() {
         // Request playback but don't succeed yet.
         mController.playTab(mTab);
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
index 56e7ad2..431d4cce 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
@@ -29,6 +29,10 @@
     public static String IS_TAB_PLAYBACK_CREATION_SUCCESSFUL =
             "ReadAloud.IsTabPlaybackCreationSuccessful";
 
+    public static String VOICE_CHANGED = "ReadAloud.VoiceChanged.";
+
+    public static String VOICE_PREVIEWED = "ReadAloud.VoicePreviewed.";
+
     /**
      * The reason why we clear the prepared message.
      *
@@ -136,4 +140,12 @@
     public static void recordPlaybackStarted() {
         RecordUserAction.record("ReadAloud.PlaybackStarted");
     }
+
+    public static void recordVoiceChanged(String voiceID) {
+        RecordHistogram.recordBooleanHistogram(VOICE_CHANGED + voiceID, true);
+    }
+
+    public static void recordVoicePreviewed(String voiceID) {
+        RecordHistogram.recordBooleanHistogram(VOICE_PREVIEWED + voiceID, true);
+    }
 }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
index 0605e81..ded68fe 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java
@@ -51,6 +51,7 @@
         if (language == null || language.isEmpty() || voiceId == null || voiceId.isEmpty()) {
             return;
         }
+        ReadAloudMetrics.recordVoiceChanged(voiceId);
         ReadAloudPrefsJni.get().setVoice(prefs, language, voiceId);
     }
 
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
index 29d41f6..5ca0a6b 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java
@@ -82,6 +82,19 @@
     }
 
     @Test
+    public void testSetVoice_metric() {
+        final String histogramName = ReadAloudMetrics.VOICE_CHANGED;
+
+        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "abc", true);
+        ReadAloudPrefs.setVoice(mPrefService, "en", "abc");
+        histogram.assertExpected();
+
+        histogram = HistogramWatcher.newSingleRecordWatcher(histogramName + "def", true);
+        ReadAloudPrefs.setVoice(mPrefService, "es", "def");
+        histogram.assertExpected();
+    }
+
+    @Test
     public void testDefaultSpeed() {
         assertEquals(1f, ReadAloudPrefs.getSpeed(mPrefService), /* delta= */ 0f);
     }
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
index abf1d05..e5b0b9c 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu_browsertest.cc
@@ -1357,13 +1357,7 @@
 }
 
 // Opens a link in a new tab via a "real" context menu.
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_RealMenu DISABLED_RealMenu
-#else
-#define MAYBE_RealMenu RealMenu
-#endif
-IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, MAYBE_RealMenu) {
+IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, RealMenu) {
   ContextMenuNotificationObserver menu_observer(
       IDC_CONTENT_CONTEXT_OPENLINKNEWTAB);
   ui_test_utils::AllBrowserTabAddedWaiter add_tab;
@@ -3055,24 +3049,12 @@
   size_t request_attempts_ = 0u;
 };
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_LoadImage DISABLED_LoadImage
-#else
-#define MAYBE_LoadImage LoadImage
-#endif
-IN_PROC_BROWSER_TEST_F(LoadImageBrowserTest, MAYBE_LoadImage) {
+IN_PROC_BROWSER_TEST_F(LoadImageBrowserTest, LoadImage) {
   SetupAndLoadImagePage("/load_image/image.html", "/load_image/image.png");
   AttemptLoadImage();
 }
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_LoadImageWithMap DISABLED_LoadImageWithMap
-#else
-#define MAYBE_LoadImageWithMap LoadImageWithMap
-#endif
-IN_PROC_BROWSER_TEST_F(LoadImageBrowserTest, MAYBE_LoadImageWithMap) {
+IN_PROC_BROWSER_TEST_F(LoadImageBrowserTest, LoadImageWithMap) {
   SetupAndLoadImagePage("/load_image/image_with_map.html",
                         "/load_image/image.png");
   AttemptLoadImage();
@@ -3176,26 +3158,14 @@
       blink::mojom::ContextMenuDataMediaType::kNone, ui::MENU_SOURCE_MOUSE);
 }
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_GifImageShare DISABLED_GifImageShare
-#else
-#define MAYBE_GifImageShare GifImageShare
-#endif
-IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, MAYBE_GifImageShare) {
+IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, GifImageShare) {
   OpenImagePageAndContextMenu("/google/logo.gif");
   RequestImageAndVerifyResponse(
       gfx::Size(2048, 2048), chrome::mojom::ImageFormat::ORIGINAL,
       gfx::Size(276, 110), gfx::Size(276, 110), ".gif");
 }
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_GifImageDownscaleToJpeg DISABLED_GifImageDownscaleToJpeg
-#else
-#define MAYBE_GifImageDownscaleToJpeg GifImageDownscaleToJpeg
-#endif
-IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, MAYBE_GifImageDownscaleToJpeg) {
+IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, GifImageDownscaleToJpeg) {
   OpenImagePageAndContextMenu("/google/logo.gif");
   RequestImageAndVerifyResponse(
       gfx::Size(100, 100), chrome::mojom::ImageFormat::ORIGINAL,
@@ -3228,15 +3198,7 @@
       gfx::Size(100, 50), ".png");
 }
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_PngImageOriginalDownscaleToPng \
-  DISABLED_PngImageOriginalDownscaleToPng
-#else
-#define MAYBE_PngImageOriginalDownscaleToPng PngImageOriginalDownscaleToPng
-#endif
-IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest,
-                       MAYBE_PngImageOriginalDownscaleToPng) {
+IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, PngImageOriginalDownscaleToPng) {
   OpenImagePageAndContextMenu("/image_search/valid.png");
   RequestImageAndVerifyResponse(
       gfx::Size(100, 100), chrome::mojom::ImageFormat::ORIGINAL,
@@ -3256,13 +3218,7 @@
       gfx::Size(480, 320), gfx::Size(100, /* 100 / 480 * 320 =  */ 66), ".jpg");
 }
 
-// TODO(crbug.com/1462760): Enable the test.
-#if BUILDFLAG(IS_MAC)
-#define MAYBE_JpgImageDownscaleToWebp DISABLED_JpgImageDownscaleToWebp
-#else
-#define MAYBE_JpgImageDownscaleToWebp JpgImageDownscaleToWebp
-#endif
-IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, MAYBE_JpgImageDownscaleToWebp) {
+IN_PROC_BROWSER_TEST_P(ContextMenuBrowserTest, JpgImageDownscaleToWebp) {
   OpenImagePageAndContextMenu("/android/watch.jpg");
   RequestImageAndVerifyResponse(
       gfx::Size(100, 100), chrome::mojom::ImageFormat::WEBP,
diff --git a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.html b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.html
index 0952fa6..c9e694b2 100644
--- a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.html
+++ b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.html
@@ -122,11 +122,7 @@
     cancel-button-label="$i18n{cancel}"
     close-only="[[getErrorTitle_(errorState_)]]">
   <div id="content" slot="content">
-    <!-- TODO(b/279623883): Remove dark mode handling. -->
-    <iron-media-query query="(prefers-color-scheme: dark)"
-        query-matches="{{isDarkModeActive_}}">
-    </iron-media-query>
-      <cros-lottie-renderer id="animation" asset-url="[[getAnimationUrl_(isDarkModeActive_, isJellyEnabled_)]]"
+      <cros-lottie-renderer id="animation" asset-url="[[getAnimationUrl_()]]"
           autoplay dynamic aria-hidden>
       </cros-lottie-renderer>
     <div id="help">
diff --git a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.ts b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.ts
index 76bc578..30bc27ad 100644
--- a/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.ts
+++ b/chrome/browser/resources/ash/settings/nearby_share_page/nearby_share_high_visibility_page.ts
@@ -12,13 +12,11 @@
 import 'chrome://resources/cros_components/lottie_renderer/lottie-renderer.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/cr_components/localized_link/localized_link.js';
-import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 import '/shared/nearby_page_template.js';
 import '/shared/nearby_shared_icons.html.js';
 
 import {RegisterReceiveSurfaceResult} from '/shared/nearby_share.mojom-webui.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './nearby_share_high_visibility_page.html.js';
@@ -33,22 +31,10 @@
   SOMETHING_WRONG = 3,
 }
 
-// TODO(TODO(b/279623883): Remove dark mode handling.
-
 /**
- * The pulse animation asset URL for light mode.
+ * The pulse animation asset URL.
  */
-const PULSE_ANIMATION_URL_LIGHT = 'nearby_share_pulse_animation_light.json';
-
-/**
- * The pulse animation asset URL for dark mode.
- */
-const PULSE_ANIMATION_URL_DARK = 'nearby_share_pulse_animation_dark.json';
-
-/**
- * The pulse animation asset URL for jelly mode.
- */
-const PULSE_ANIMATION_URL_JELLY = 'nearby_share_pulse_animation_jelly.json';
+const PULSE_ANIMATION_URL = 'nearby_share_pulse_animation.json';
 
 const NearbyShareHighVisibilityPageElementBase = I18nMixin(PolymerElement);
 
@@ -113,26 +99,6 @@
             'computeErrorState_(shutoffTimestamp, remainingTimeInSeconds_,' +
             'registerResult, nearbyProcessStopped, startAdvertisingFailed)',
       },
-
-      /**
-       * Whether the high visibility page is being rendered in dark mode.
-       */
-      isDarkModeActive_: {
-        type: Boolean,
-        value: false,
-      },
-
-      /**
-       * Return true if the Jelly feature flag is enabled.
-       */
-      isJellyEnabled_: {
-        type: Boolean,
-        readOnly: true,
-        value() {
-          return loadTimeData.valueExists('isJellyEnabled') &&
-              loadTimeData.getBoolean('isJellyEnabled');
-        },
-      },
     };
   }
 
@@ -142,8 +108,6 @@
   shutoffTimestamp: number;
   startAdvertisingFailed: boolean;
   private errorState_: NearbyVisibilityErrorState|null;
-  private isDarkModeActive_: boolean;
-  private isJellyEnabled_: boolean;
   private remainingTimeInSeconds_: number;
   private remainingTimeIntervalId_: number;
 
@@ -287,14 +251,7 @@
    * pulsing background animation.
    */
   private getAnimationUrl_(): string {
-    if (this.isJellyEnabled_) {
-      return PULSE_ANIMATION_URL_JELLY;
-    }
-
-    // TODO(b/279623883): Clean up dark mode logic and duplicate assets after
-    // Jelly is launched.
-    return this.isDarkModeActive_ ? PULSE_ANIMATION_URL_DARK :
-                                    PULSE_ANIMATION_URL_LIGHT;
+    return PULSE_ANIMATION_URL;
   }
 }
 
diff --git a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
index e0b4f7e..8d96dc66 100644
--- a/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
+++ b/chrome/browser/resources/ash/settings/os_apps_page/app_management_page/arc_detail_view.html
@@ -2,6 +2,10 @@
   #noPermissions {
     border-top: none;
   }
+
+  #appDetails {
+    padding-bottom: 12px;
+  }
 </style>
 <div class="permission-list">
   <app-management-pin-to-shelf-item
diff --git a/chrome/browser/resources/ash/settings/os_settings_page/os_settings_subpage.html b/chrome/browser/resources/ash/settings/os_settings_page/os_settings_subpage.html
index 966d6e2..e85c707 100644
--- a/chrome/browser/resources/ash/settings/os_settings_page/os_settings_subpage.html
+++ b/chrome/browser/resources/ash/settings/os_settings_page/os_settings_subpage.html
@@ -68,6 +68,13 @@
     /* Keep normal icon spacing from subpage-title-extra controls. */
     margin-inline-start: 16px;
   }
+
+  /* Use ID to increase specificity over cros-color-overrides. */
+  :host-context(body.revamp-wayfinding-enabled)
+      #searchField::part(searchInput) {
+    /* Add contrast between the search input and the subpage backdrop. */
+    --cr-input-background-color: var(--cros-sys-input_field_on_shaded);
+  }
 </style>
 <div id="subpageHeader" class="cr-row first">
   <cr-icon-button id="closeButton" class="icon-arrow-back"
@@ -86,7 +93,8 @@
     </cr-icon-button>
   </template>
   <template is="dom-if" if="[[searchLabel]]">
-    <cr-search-field label="[[searchLabel]]"
+    <cr-search-field id="searchField"
+        label="[[searchLabel]]"
         on-search-changed="onSearchChanged_"
         clear-label="$i18n{clearSearch}">
     </cr-search-field>
diff --git a/chrome/browser/resources/ash/settings/os_settings_search_box/os_search_result_row.html b/chrome/browser/resources/ash/settings/os_settings_search_box/os_search_result_row.html
index a73a8dc..37b9263e 100644
--- a/chrome/browser/resources/ash/settings/os_settings_search_box/os_search_result_row.html
+++ b/chrome/browser/resources/ash/settings/os_settings_search_box/os_search_result_row.html
@@ -28,6 +28,7 @@
     display: flex;
     height: 48px;
     justify-content: center;
+    font: var(--cros-body-2-font);
   }
 
   #resultText {
diff --git a/chrome/browser/resources/ash/settings/os_settings_search_box/os_settings_search_box.html b/chrome/browser/resources/ash/settings/os_settings_search_box/os_settings_search_box.html
index 361a3485..9a51c848 100644
--- a/chrome/browser/resources/ash/settings/os_settings_search_box/os_settings_search_box.html
+++ b/chrome/browser/resources/ash/settings/os_settings_search_box/os_settings_search_box.html
@@ -50,8 +50,8 @@
     --cr-toolbar-icon-button-focus-outline-color:
         var(--cros-focus-ring-color);
     --cr-toolbar-field-max-width: var(--cr-toolbar-field-width);
+    font: var(--cros-body-2-font);
     height: var(--settings-toolbar-search-box-height);
-    font-size: 13px;
   }
 
   :host([narrow][showing-search]) cr-toolbar-search-field {
@@ -125,6 +125,7 @@
     height: 32px;
     line-height: 32px;
     margin-inline-start: 24px;
+    font: var(--cros-body-2-font);
   }
 
   #reportSearchResult {
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_overview_tracing_ui.js b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_overview_tracing_ui.js
index c471ea4..e05c5e5 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_overview_tracing_ui.js
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_overview_tracing_ui.js
@@ -49,15 +49,6 @@
  */
 const targetFrameTime = 16667;
 
-// Graphics event types which are used in the model JSON data. These must match
-// the graphics event types in
-// chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h. To aid in
-// maintaining consistency, do not modify values once added - deprecation and
-// removal are allowed.
-const kExoSurfaceCommit = 206;
-const kChromeOSPresentationDone = 503;
-const kChromeOSSwapDone = 504;
-
 function initializeOverviewUi() {
   initializeUi(8 /* zoomLevel */, function() {
     // Update function.
@@ -696,8 +687,10 @@
  * @param {number} duration length of the chart in microseconds.
  * @param {string} title the title of the view
  * @param {number} eventType type of event whose rate to track
+ * @param {number} jankEventType type of event indicating a jank (optional)
  */
-function addDeltaView(parent, resolution, duration, title, eventType) {
+function addDeltaView(
+    parent, resolution, duration, title, eventType, jankEventType) {
   // time range from 0 to 67ms. 66.67ms is for 15 FPS.
   // 1 ms 1 pixel resolution.  Each grid lines correspond 1/120 FPS time update.
   const bands = createChart(
@@ -716,6 +709,14 @@
     const timeEvents = createDeltaEvents(events);
     attributes.color = modelColors.get(models[i]);
     bands.addChartSources([timeEvents], false /* smooth */, attributes);
+    if (jankEventType) {
+      // Offset each model's janks at a different y position, avoiding max and
+      // min positions (0 or 1), as these are awkward when the models are few.
+      const y = (i + 1) / (models.length + 1);
+      bands.addGlobal(
+          getGraphicsEvents(models[i], jankEventType), 'circle',
+          attributes.color, y);
+    }
   }
 }
 
@@ -790,14 +791,16 @@
 
   addFPSView(parent, resolution, duration, 'App FPS', kExoSurfaceCommit);
   addDeltaView(
-      parent, resolution, duration, 'App commit time', kExoSurfaceCommit);
+      parent, resolution, duration, 'App commit time', kExoSurfaceCommit,
+      kExoSurfaceCommitJank);
   addDeltaView(
-      parent, resolution, duration, 'ChromeOS swap time', kChromeOSSwapDone);
+      parent, resolution, duration, 'ChromeOS swap time', kChromeOSSwapDone,
+      kChromeOSSwapJank);
   addFPSView(
       parent, resolution, duration, 'Perceived FPS', kChromeOSPresentationDone);
   addDeltaView(
       parent, resolution, duration, 'Perceived swap time',
-      kChromeOSPresentationDone);
+      kChromeOSPresentationDone, kChromeOSPerceivedJank);
 
   addFPSHistograms(
       parent, parent.lastChild, false /* timeBasedView */,
diff --git a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_tracing_ui.js b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_tracing_ui.js
index 6daf1bf..e0ba440 100644
--- a/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_tracing_ui.js
+++ b/chrome/browser/resources/chromeos/arc_graphics_tracing/arc_tracing_ui.js
@@ -35,6 +35,18 @@
 // Active zoom level, as index in |zooms|. By default 100 mcs per pixel.
 let zoomLevel = 5;
 
+// Graphics event types which are used in the model JSON data. These must match
+// the graphics event types in
+// chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h. To aid in
+// maintaining consistency, do not modify values once added - deprecation and
+// removal are allowed.
+const kExoSurfaceCommit = 206;
+const kExoSurfaceCommitJank = 207;
+const kChromeOSPresentationDone = 503;
+const kChromeOSSwapDone = 504;
+const kChromeOSPerceivedJank = 506;
+const kChromeOSSwapJank = 507;
+
 /**
  * Keep in sync with ArcTracingGraphicsModel::EventType
  * See chrome/browser/ash/arc/tracing/arc_tracing_graphics_model.h.
@@ -67,6 +79,8 @@
   // kBufferFillJank
   106: {color: '#ff0000', name: 'buffer filling jank', width: 1.0, radius: 4.0},
 
+  [kExoSurfaceCommitJank]: {name: 'commit jank', radius: 4.0},
+
   // kChromeBarrierOrder.
   300: {color: '#ff9933', name: 'barrier order'},
   // kChromeBarrierFlush
@@ -98,6 +112,14 @@
     width: 1.0,
     radius: 4.0,
   },
+  [kChromeOSPerceivedJank]: {
+    name: 'perceived jank',
+    radius: 4.0,
+  },
+  [kChromeOSSwapJank]: {
+    name: 'swap jank',
+    radius: 4.0,
+  },
 
   // kCustomEvent
   600: {color: '#7cb342', name: 'Custom event', width: 1.0, radius: 4.0},
@@ -915,9 +937,18 @@
    * @param {Events} events to add.
    * @param {string} renderType defines how to render events, can be underfined
    *                 for default or set to 'circle'.
+   * @param {string} color the color (fill color if rendered as a circle), or
+   *                 omitted to use the color defined in eventAttributes for
+   *                 each event type.
+   * @param {number} y for circles, the y position, as a fraction of this band's
+   *                 height, such as 0.5 for vertically-centered, or 0.95 for
+   *                 close to the bottom.
    */
-  addGlobal(events, renderType) {
+  addGlobal(events, renderType, color, y) {
     let eventIndex = -1;
+    if (typeof y == 'undefined') {
+      y = 0.5;
+    }
     while (true) {
       eventIndex = events.getNextEvent(eventIndex, 1 /* direction */);
       if (eventIndex < 0) {
@@ -926,13 +957,13 @@
       const event = events.events[eventIndex];
       const attributes = events.getEventAttributes(eventIndex);
       const x = this.timestampToOffset(event[1]) + this.bandOffsetX;
+      const evColor = color || attributes.color;
       if (renderType == 'circle') {
         SVG.addCircle(
-            this.svg, x, this.height / 2, attributes.radius,
-            1 /* strokeWidth */, attributes.color, 'black' /* strokeColor */);
+            this.svg, x, this.height * y, attributes.radius,
+            1 /* strokeWidth */, evColor, 'black' /* strokeColor */);
       } else {
-        SVG.addLine(
-            this.svg, x, 0, x, this.height, attributes.color, attributes.width);
+        SVG.addLine(this.svg, x, 0, x, this.height, evColor, attributes.width);
       }
     }
     this.globalEvents.push(events);
diff --git a/chrome/browser/resources/compose/app.html b/chrome/browser/resources/compose/app.html
index 909a0d4..f6b0550 100644
--- a/chrome/browser/resources/compose/app.html
+++ b/chrome/browser/resources/compose/app.html
@@ -507,6 +507,11 @@
         inert$="[[hasPartialResponse_(partialResponse_)]]">
       <div class="footer-text">
         <div on-click="onFooterClick_">
+          <b id="onDeviceUsedFooter"
+              hidden$="[[!onDeviceEvaluationUsed_(response_)]]"
+              >$i18nRaw{onDeviceUsedFooter}
+
+          </b>
           $i18nRaw{resultFooter}
         </div>
         <cr-feedback-buttons
diff --git a/chrome/browser/resources/compose/app.ts b/chrome/browser/resources/compose/app.ts
index 8a2a7f7..b8ad172 100644
--- a/chrome/browser/resources/compose/app.ts
+++ b/chrome/browser/resources/compose/app.ts
@@ -61,6 +61,7 @@
     submitButton: CrButtonElement,
     submitEditButton: CrButtonElement,
     submitFooter: HTMLElement,
+    onDeviceUsedFooter: HTMLElement,
     textarea: ComposeTextareaElement,
     lengthMenu: HTMLSelectElement,
     toneMenu: HTMLSelectElement,
@@ -559,6 +560,10 @@
     return this.response_.status !== ComposeStatus.kOk;
   }
 
+  private onDeviceEvaluationUsed_(): boolean {
+    return Boolean(this.response_?.onDeviceEvaluationUsed);
+  }
+
   private acceptButtonText_(): string {
     return this.textSelected_ ? this.i18n('replaceButton') :
                                 this.i18n('insertButton');
diff --git a/chrome/browser/resources/compose/textarea.ts b/chrome/browser/resources/compose/textarea.ts
index dadfe23..aa2ba1220 100644
--- a/chrome/browser/resources/compose/textarea.ts
+++ b/chrome/browser/resources/compose/textarea.ts
@@ -110,9 +110,11 @@
   validate() {
     const value = this.$.input.value;
     const wordCount = value.match(/\S+/g)?.length || 0;
-    this.tooShort_ = wordCount < this.inputParams.minWordLimit;
     this.tooLong_ = value.length > this.inputParams.maxCharacterLimit ||
         wordCount > this.inputParams.maxWordLimit;
+    // If it's too long, then it can't be too short.
+    this.tooShort_ =
+        wordCount < this.inputParams.minWordLimit && !this.tooLong_;
     this.invalidInput_ = this.tooLong_ || this.tooShort_;
     return !this.invalidInput_;
   }
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.html b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
index eebacce..589bdd4 100644
--- a/chrome/browser/resources/nearby_share/nearby_discovery_page.html
+++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
@@ -229,11 +229,7 @@
         cancel-button-event-name="close"
         close-only="[[errorTitle_]]">
   <div id="centerContent" slot="content">
-    <!-- TODO(b/279623883): Remove dark mode handling. -->
-    <iron-media-query query="(prefers-color-scheme: dark)"
-        query-matches="{{isDarkModeActive_}}">
-    </iron-media-query>
-      <cros-lottie-renderer id="animation" asset-url="[[getAnimationUrl_(isDarkModeActive_, isJellyEnabled_)]]"
+      <cros-lottie-renderer id="animation" asset-url="[[getAnimationUrl_()]]"
           autoplay dynamic aria-hidden>
       </cros-lottie-renderer>
     <div id="process-row">
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.ts b/chrome/browser/resources/nearby_share/nearby_discovery_page.ts
index 296fb30..4630785 100644
--- a/chrome/browser/resources/nearby_share/nearby_discovery_page.ts
+++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.ts
@@ -43,26 +43,10 @@
   return a.high === b.high && a.low === b.low;
 }
 
-// TODO(TODO(b/279623883): Remove dark mode handling.
-
 /**
- * The pulse animation asset URL for light mode.
+ * The pulse animation asset URL.
  */
-const PULSE_ANIMATION_URL_LIGHT: string =
-    'nearby_share_pulse_animation_light.json';
-
-/**
- * The pulse animation asset URL for dark mode.
- */
-const PULSE_ANIMATION_URL_DARK: string =
-    'nearby_share_pulse_animation_dark.json';
-
-/**
- * The pulse animation asset URL for jelly mode.
- */
-const PULSE_ANIMATION_URL_JELLY: string =
-    'nearby_share_pulse_animation_jelly.json';
-
+const PULSE_ANIMATION_URL: string = 'nearby_share_pulse_animation.json';
 
 const NearbyDiscoveryPageElementBase = I18nMixin(PolymerElement);
 
@@ -160,26 +144,6 @@
       },
 
       /**
-       * Whether the discovery page is being rendered in dark mode.
-       */
-      isDarkModeActive_: {
-        type: Boolean,
-        value: false,
-      },
-
-      /**
-       * Return true if the Jelly feature flag is enabled.
-       */
-      isJellyEnabled_: {
-        type: Boolean,
-        readOnly: true,
-        value() {
-          return loadTimeData.valueExists('isJellyEnabled') &&
-              loadTimeData.getBoolean('isJellyEnabled');
-        },
-      },
-
-      /**
        * Return true if the Nearby Share Self Share feature flag is enabled.
        */
       isSelfShareEnabled: {
@@ -204,8 +168,6 @@
   private nonSelfShareTargets_: ShareTarget[];
   private errorTitle_: string|null;
   private errorDescription_: string|null;
-  private isDarkModeActive_: boolean;
-  private isJellyEnabled_: boolean;
 
   private mojoEventTarget_: ShareTargetListenerCallbackRouter|null = null;
   private listenerIds_: number[]|null = null;
@@ -654,14 +616,7 @@
    * pulsing background animation
    */
   private getAnimationUrl_(): string {
-    if (this.isJellyEnabled_) {
-      return PULSE_ANIMATION_URL_JELLY;
-    }
-
-    // TODO(b/279623883): Clean up dark mode logic and duplicate assets after
-    // Jelly is launched.
-    return this.isDarkModeActive_ ? PULSE_ANIMATION_URL_DARK :
-                                    PULSE_ANIMATION_URL_LIGHT;
+    return PULSE_ANIMATION_URL;
   }
 
   /**
diff --git a/chrome/browser/resources/nearby_share/shared/BUILD.gn b/chrome/browser/resources/nearby_share/shared/BUILD.gn
index 1cccbb6b..e356f80d 100644
--- a/chrome/browser/resources/nearby_share/shared/BUILD.gn
+++ b/chrome/browser/resources/nearby_share/shared/BUILD.gn
@@ -67,8 +67,6 @@
   input_files_base_dir = rebase_path(".", "//")
   input_files = [
     "nearby_share_progress_bar.json",
-    "nearby_share_pulse_animation_dark.json",
-    "nearby_share_pulse_animation_light.json",
-    "nearby_share_pulse_animation_jelly.json",
+    "nearby_share_pulse_animation.json",
   ]
 }
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_jelly.json b/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation.json
similarity index 100%
rename from chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_jelly.json
rename to chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation.json
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_dark.json b/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_dark.json
deleted file mode 100644
index 77c1f76..0000000
--- a/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_dark.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.7.4","fr":60,"ip":0,"op":189,"w":783,"h":257,"nm":"ripple","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 2 Outlines","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2,"l":2},"a":{"a":0,"k":[64,64,0],"ix":1,"l":2},"s":{"a":0,"k":[303,303,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13.254,0],[0,-13.255],[0,0],[0,0]],"o":[[-13.255,0],[0,0],[0,0],[0,-13.255]],"v":[[0,-12],[-24,12],[2.187,12],[24,12]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.541176470588,0.705882352941,0.972549019608,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[63.999,52],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1440,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 1 Outlines 3","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2,"l":2},"a":{"a":0,"k":[64,64,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":58,"s":[113,113,100]},{"t":189,"s":[352,352,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.541176470588,0.705882352941,0.972549019608,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":58,"s":[3]},{"t":189,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":59,"op":1496,"st":56,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 1 Outlines 2","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2,"l":2},"a":{"a":0,"k":[64,64,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":30,"s":[113,113,100]},{"t":161,"s":[352,352,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.541176470588,0.705882352941,0.972549019608,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":30,"s":[3]},{"t":161,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":31,"op":1468,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 1 Outlines","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2,"l":2},"a":{"a":0,"k":[64,64,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":2,"s":[113,113,100]},{"t":133,"s":[352,352,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.541176470588,0.705882352941,0.972549019608,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":2,"s":[3]},{"t":133,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":1440,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_light.json b/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_light.json
deleted file mode 100644
index 0a2d4c1..0000000
--- a/chrome/browser/resources/nearby_share/shared/nearby_share_pulse_animation_light.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.6.4","fr":60,"ip":0,"op":189,"w":783,"h":257,"nm":"ripple","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 2 Outlines","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[303,303,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[13.254,0],[0,-13.255],[0,0],[0,0]],"o":[[-13.255,0],[0,0],[0,0],[0,-13.255]],"v":[[0,-12],[-24,12],[2.187,12],[24,12]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[63.999,52],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1440,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 1 Outlines 3","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":58,"s":[113,113,100]},{"t":189,"s":[352,352,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":58,"s":[3]},{"t":189,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":59,"op":1496,"st":56,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 1 Outlines 2","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":30,"s":[113,113,100]},{"t":161,"s":[352,352,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":30,"s":[3]},{"t":161,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":31,"op":1468,"st":28,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 1 Outlines","sr":1,"ks":{"o":{"a":0,"k":10,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[391.562,256.875,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.4,0.4,0.4],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":2,"s":[113,113,100]},{"t":133,"s":[352,352,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[34.518,0],[0,-34.518]],"o":[[0,-34.518],[-34.519,0],[0,0]],"v":[[62.5,31.25],[0,-31.25],[-62.5,31.25]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.001],"y":[0]},"t":2,"s":[3]},{"t":133,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64,32.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":3,"op":1440,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
index 1962760..730d8b1 100644
--- a/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
+++ b/chrome/browser/resources/privacy_sandbox/internals/content_setting_pattern_source.ts
@@ -11,7 +11,7 @@
 
 import {getTemplate} from './content_setting_pattern_source.html.js';
 import {ContentSetting, ContentSettingPatternSource, SessionModel} from './content_settings.mojom-webui.js';
-import {PageHandlerRemote} from './privacy_sandbox_internals.mojom-webui.js';
+import {PageHandlerInterface} from './privacy_sandbox_internals.mojom-webui.js';
 import {defaultLogicalFn, LogicalFn} from './value_display.js';
 
 function contentSettingLogicalValue(v: Value) {
@@ -40,7 +40,7 @@
   return el;
 }
 
-class ContentSettingPatternSourceElement extends CustomElement {
+export class ContentSettingPatternSourceElement extends CustomElement {
   static observedAttributes = ['collapsed'];
 
   static override get template() {
@@ -90,23 +90,25 @@
     }
   }
 
-  configure(pageHandler: PageHandlerRemote, cs: ContentSettingPatternSource) {
-    pageHandler.contentSettingsPatternToString(cs.primaryPattern)
-        .then(
-            (obj) => {
-              this.setField('primary-pattern', obj.s);
-            },
-            (err) => {
-              console.error(err);
-            });
-    pageHandler.contentSettingsPatternToString(cs.secondaryPattern)
-        .then(
-            (obj) => {
-              this.setField('secondary-pattern', obj.s);
-            },
-            (err) => {
-              console.error(err);
-            });
+  async configure(
+      pageHandler: PageHandlerInterface, cs: ContentSettingPatternSource) {
+    try {
+      this.setField(
+          'primary-pattern',
+          (await pageHandler.contentSettingsPatternToString(cs.primaryPattern))
+              .s);
+    } catch (e) {
+      console.error('Error parsing primary pattern ', e);
+    }
+    try {
+      this.setField(
+          'secondary-pattern',
+          (await pageHandler.contentSettingsPatternToString(
+               cs.secondaryPattern))
+              .s);
+    } catch (e) {
+      console.error('Error parsing secondary pattern ', e);
+    }
     this.setFieldValue('value', cs.settingValue, contentSettingLogicalValue);
     this.setField('source', cs.source);
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
index 714b964..eb7678bb 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/BUILD.gn
@@ -13,6 +13,7 @@
     "chrome_web_store.svg",
     "corner_new_tab_page.svg",
     "coupons.svg",
+    "delete.svg",
     "generated_image.svg",
     "gm3_corner_new_tab_page.svg",
     "gm3_mini_new_tab_page.svg",
diff --git a/chrome/browser/resources/side_panel/customize_chrome/icons/delete.svg b/chrome/browser/resources/side_panel/customize_chrome/icons/delete.svg
new file mode 100644
index 0000000..b1ac98c
--- /dev/null
+++ b/chrome/browser/resources/side_panel/customize_chrome/icons/delete.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path fill="#1F1F1F" d="M6.5 17c-.417 0-.77-.146-1.063-.438A1.447 1.447 0 0 1 5 15.5v-10H4V4h4V3h4v1h4v1.5h-1v10c0 .417-.146.77-.438 1.063A1.446 1.446 0 0 1 13.5 17h-7Zm7-11.5h-7v10h7v-10ZM8 14h1.5V7H8v7Zm2.5 0H12V7h-1.5v7Zm-4-8.5v10-10Z"/></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
index 5730e57..4e3e075 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
@@ -150,6 +150,12 @@
     width: 16px;
   }
 
+  #deleteSelectedHueButton {
+    --cr-icon-image: url(icons/delete.svg);
+    margin-inline-start: 0;
+    margin-inline-end: -24px;
+  }
+
   #btnContainer {
     display: flex;
     justify-content: flex-end;
@@ -311,6 +317,12 @@
     </cr-grid>
     <cr-theme-hue-slider-dialog id="hueSlider"
         on-selected-hue-changed="onSelectedHueChanged_">
+      <cr-icon-button slot="headerSuffix" id="deleteSelectedHueButton"
+          hidden$="[[!shouldShowDeleteSelectedHueButton_(selectedHue_)]]"
+          title="$i18n{hueSliderDeleteTitle}"
+          aria-label="$i18n{hueSliderDeleteA11yLabel}"
+          on-click="onSelectedHueDelete_">
+      </cr-icon-button>
     </cr-theme-hue-slider-dialog>
     <div id="btnContainer">
       <cr-button
@@ -405,7 +417,7 @@
     <cr-grid columns="3" disable-arrow-navigation>
       <template is="dom-repeat" id="historyRepeat" items="[[history_]]">
         <div class="tile result" tabindex="0" role="button"
-            aria-label$="[[getHistoryTileTitle_(index)]]"
+            aria-label$="[[getHistoryResultAriaLabel_(index, item)]]"
             on-click="onHistoryImageClick_"
             aria-current$="[[getBackgroundCheckedStatus_(item.id, theme_)]]">
           <customize-chrome-check-mark-wrapper class="image-check-mark"
diff --git a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
index 507119e..b456ce9a 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
@@ -35,7 +35,7 @@
 import {CustomizeChromeAction, recordCustomizeChromeAction} from '../common.js';
 import {CustomizeChromePageCallbackRouter, CustomizeChromePageHandlerInterface, Theme} from '../customize_chrome.mojom-webui.js';
 import {CustomizeChromeApiProxy} from '../customize_chrome_api_proxy.js';
-import {DescriptorA, DescriptorB, DescriptorDValue, Descriptors, UserFeedback, WallpaperSearchClientCallbackRouter, WallpaperSearchHandlerInterface, WallpaperSearchResult, WallpaperSearchStatus} from '../wallpaper_search.mojom-webui.js';
+import {DescriptorA, DescriptorB, DescriptorDValue, Descriptors, ResultDescriptors, UserFeedback, WallpaperSearchClientCallbackRouter, WallpaperSearchHandlerInterface, WallpaperSearchResult, WallpaperSearchStatus} from '../wallpaper_search.mojom-webui.js';
 import {WindowProxy} from '../window_proxy.js';
 
 import {ComboboxGroup, ComboboxItem, CustomizeChromeCombobox} from './combobox/customize_chrome_combobox.js';
@@ -92,6 +92,7 @@
 export interface WallpaperSearchElement {
   $: {
     customColorContainer: HTMLElement,
+    deleteSelectedHueButton: HTMLElement,
     descriptorComboboxA: CustomizeChromeCombobox,
     descriptorComboboxB: CustomizeChromeCombobox,
     descriptorComboboxC: CustomizeChromeCombobox,
@@ -181,7 +182,10 @@
         type: Object,
         observer: 'onColorDescriptorChange_',
       },
-      selectedHue_: Number,
+      selectedHue_: {
+        type: Number,
+        value: null,
+      },
       status_: {
         type: WallpaperSearchStatus,
         value: WallpaperSearchStatus.kOk,
@@ -213,7 +217,7 @@
   private selectedDescriptorC_: string|null;
   private selectedDescriptorD_: DescriptorDValue|null;
   private selectedFeedbackOption_: CrFeedbackOption;
-  private selectedHue_: number|undefined;
+  private selectedHue_: number|null;
   private status_: WallpaperSearchStatus;
   private theme_: Theme|undefined;
 
@@ -405,12 +409,31 @@
   }
 
   private getCustomColorCheckedStatus_(): string {
-    return this.selectedHue_ !== undefined ? 'true' : 'false';
+    return this.selectedHue_ !== null ? 'true' : 'false';
   }
 
-  private getHistoryTileTitle_(index: number): string {
+  private getHistoryResultAriaLabel_(
+      index: number, result: WallpaperSearchResult): string {
+    if (!result.descriptors || !result.descriptors.subject) {
+      return loadTimeData.getStringF(
+          'wallpaperSearchHistoryResultLabelNoDescriptor', index + 1);
+    } else if (result.descriptors.style && result.descriptors.mood) {
+      return loadTimeData.getStringF(
+          'wallpaperSearchHistoryResultLabelBC', index + 1,
+          result.descriptors.subject, result.descriptors.style,
+          result.descriptors.mood);
+    } else if (result.descriptors.style) {
+      return loadTimeData.getStringF(
+          'wallpaperSearchHistoryResultLabelB', index + 1,
+          result.descriptors.subject, result.descriptors.style);
+    } else if (result.descriptors.mood) {
+      return loadTimeData.getStringF(
+          'wallpaperSearchHistoryResultLabelC', index + 1,
+          result.descriptors.subject, result.descriptors.mood);
+    }
     return loadTimeData.getStringF(
-        'wallpaperSearchHistoryTileTitle', index + 1);
+        'wallpaperSearchHistoryResultLabel', index + 1,
+        result.descriptors.subject);
   }
 
   private getResultAriaLabel_(index: number): string {
@@ -470,7 +493,7 @@
   }
 
   private onDefaultColorClick_(e: DomRepeatEvent<string>) {
-    this.selectedHue_ = undefined;
+    this.selectedHue_ = null;
     this.selectedDefaultColor_ = e.model.item;
     this.selectedDescriptorD_ = {
       color: hexColorToSkColor(this.selectedDefaultColor_),
@@ -520,7 +543,8 @@
   private onHistoryImageClick_(e: DomRepeatEvent<WallpaperSearchResult>) {
     recordCustomizeChromeAction(
         CustomizeChromeAction.WALLPAPER_SEARCH_HISTORY_IMAGE_SELECTED);
-    this.wallpaperSearchHandler_.setBackgroundToHistoryImage(e.model.item.id);
+    this.wallpaperSearchHandler_.setBackgroundToHistoryImage(
+        e.model.item.id, e.model.item.descriptors ?? {});
   }
 
   private onLearnMoreClick_(e: Event) {
@@ -528,12 +552,19 @@
     this.wallpaperSearchHandler_.openHelpArticle();
   }
 
-  private async onSelectedHueChanged_() {
+  private onSelectedHueChanged_() {
     this.selectedDefaultColor_ = undefined;
     this.selectedHue_ = this.$.hueSlider.selectedHue;
     this.selectedDescriptorD_ = {hue: this.selectedHue_};
   }
 
+  private onSelectedHueDelete_() {
+    this.selectedHue_ = null;
+    this.selectedDescriptorD_ = null;
+    this.$.hueSlider.hide();
+    this.$.customColorContainer.focus();
+  }
+
   private async onSearchClick_() {
     if (!WindowProxy.getInstance().onLine) {
       this.status_ = WallpaperSearchStatus.kOffline;
@@ -583,8 +614,14 @@
   private async onResultClick_(e: DomRepeatEvent<WallpaperSearchResult>) {
     recordCustomizeChromeAction(
         CustomizeChromeAction.WALLPAPER_SEARCH_RESULT_IMAGE_SELECTED);
+    const descriptors: ResultDescriptors = {
+      subject: this.selectedDescriptorA_!,
+      style: this.selectedDescriptorB_ ?? undefined,
+      mood: this.selectedDescriptorC_ ?? undefined,
+      color: this.selectedDescriptorD_ ?? undefined,
+    };
     this.wallpaperSearchHandler_.setBackgroundToWallpaperSearchResult(
-        e.model.item.id, WindowProxy.getInstance().now());
+        e.model.item.id, WindowProxy.getInstance().now(), descriptors);
   }
 
   private onStatusChange_() {
@@ -595,6 +632,10 @@
     }
   }
 
+  private shouldShowDeleteSelectedHueButton_() {
+    return this.selectedHue_ !== null;
+  }
+
   private shouldShowFeedbackButtons_() {
     return !this.loading_ && this.results_.length > 0;
   }
diff --git a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.cc b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.cc
index 6381c0d..6c27b89 100644
--- a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.cc
+++ b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.cc
@@ -30,6 +30,9 @@
 namespace {
 
 const char kWallpaperSearchHistoryId[] = "id";
+const char kWallpaperSearchHistoryMood[] = "mood";
+const char kWallpaperSearchHistoryStyle[] = "style";
+const char kWallpaperSearchHistorySubject[] = "subject";
 
 void WriteFileToPath(const std::string& data, const base::FilePath& path) {
   base::WriteFile(path, base::as_bytes(base::make_span(data)));
@@ -47,12 +50,29 @@
 absl::optional<HistoryEntry> GetHistoryEntryFromPrefValue(
     const base::Value& pref_value) {
   if (pref_value.is_dict()) {
+    const base::Value::Dict& pref_dict = pref_value.GetDict();
     const std::string* id_string =
-        pref_value.GetDict().FindString(kWallpaperSearchHistoryId);
+        pref_dict.FindString(kWallpaperSearchHistoryId);
     if (id_string) {
       absl::optional<base::Token> id = base::Token::FromString(*id_string);
       if (id.has_value()) {
-        return HistoryEntry(*id);
+        HistoryEntry history_entry = HistoryEntry(*id);
+        const std::string* subject_string =
+            pref_dict.FindString(kWallpaperSearchHistorySubject);
+        if (subject_string) {
+          history_entry.subject = *subject_string;
+        }
+        const std::string* style_string =
+            pref_dict.FindString(kWallpaperSearchHistoryStyle);
+        if (style_string) {
+          history_entry.style = *style_string;
+        }
+        const std::string* mood_string =
+            pref_dict.FindString(kWallpaperSearchHistoryMood);
+        if (mood_string) {
+          history_entry.mood = *mood_string;
+        }
+        return history_entry;
       }
     }
   }
@@ -60,8 +80,19 @@
 }
 
 base::Value::Dict GetHistoryEntryDict(const HistoryEntry& history_entry) {
-  return base::Value::Dict().Set(kWallpaperSearchHistoryId,
-                                 history_entry.id.ToString());
+  base::Value::Dict history_entry_dict = base::Value::Dict().Set(
+      kWallpaperSearchHistoryId, history_entry.id.ToString());
+  if (history_entry.subject) {
+    history_entry_dict.Set(kWallpaperSearchHistorySubject,
+                           *history_entry.subject);
+  }
+  if (history_entry.style) {
+    history_entry_dict.Set(kWallpaperSearchHistoryStyle, *history_entry.style);
+  }
+  if (history_entry.mood) {
+    history_entry_dict.Set(kWallpaperSearchHistoryMood, *history_entry.mood);
+  }
+  return history_entry_dict;
 }
 
 }  // namespace
@@ -123,14 +154,14 @@
 
 WallpaperSearchBackgroundManager::~WallpaperSearchBackgroundManager() = default;
 
-std::vector<base::Token> WallpaperSearchBackgroundManager::GetHistory() {
+std::vector<HistoryEntry> WallpaperSearchBackgroundManager::GetHistory() {
   auto& history_list =
       pref_service_->GetList(prefs::kNtpWallpaperSearchHistory);
-  std::vector<base::Token> history;
+  std::vector<HistoryEntry> history;
   for (auto& entry : history_list) {
     const auto entry_obj = GetHistoryEntryFromPrefValue(entry);
     if (entry_obj) {
-      history.push_back(entry_obj->id);
+      history.push_back(*entry_obj);
     }
   }
   return history;
@@ -195,10 +226,10 @@
     const HistoryEntry& history_entry) {
   absl::optional<CustomBackground> current_theme =
       ntp_custom_background_service_->GetCustomBackground();
-  std::string entry_id_str = history_entry.id.ToString();
   if (current_theme.has_value() &&
       current_theme->local_background_id.has_value() &&
-      current_theme->local_background_id->ToString() == entry_id_str) {
+      current_theme->local_background_id->ToString() ==
+          history_entry.id.ToString()) {
     const base::Value::List& current_history =
         pref_service_->GetList(prefs::kNtpWallpaperSearchHistory);
     base::Value::List new_history =
@@ -209,14 +240,13 @@
     for (const auto& value : current_history) {
       const auto value_obj = GetHistoryEntryFromPrefValue(value);
       if (value_obj) {
-        const std::string id_str = value_obj->id.ToString();
-        if (id_str != entry_id_str) {
+        if (value_obj.value() != history_entry) {
           if (new_history.size() >= 6) {
             // Delete values that will no longer be in the history.
-            DeleteWallpaperSearchImage(id_str, profile_->GetPath());
+            DeleteWallpaperSearchImage(value_obj->id.ToString(),
+                                       profile_->GetPath());
           } else {
-            new_history.Append(
-                base::Value::Dict().Set(kWallpaperSearchHistoryId, id_str));
+            new_history.Append(GetHistoryEntryDict(value_obj.value()));
           }
         }
       }
diff --git a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.h b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.h
index 7767cea..2f43833 100644
--- a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.h
+++ b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager.h
@@ -30,7 +30,7 @@
   virtual ~WallpaperSearchBackgroundManager();
 
   // Gets the current history list.
-  virtual std::vector<base::Token> GetHistory();
+  virtual std::vector<HistoryEntry> GetHistory();
 
   // Sets a history image to the NTP background and sets matching theme color.
   virtual void SelectHistoryImage(const base::Token& id,
diff --git a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager_unittest.cc b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager_unittest.cc
index d2a7b5a2..b3a6216e8 100644
--- a/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager_unittest.cc
+++ b/chrome/browser/search/background/wallpaper_search/wallpaper_search_background_manager_unittest.cc
@@ -28,6 +28,9 @@
 namespace {
 
 const char kWallpaperSearchHistoryId[] = "id";
+const char kWallpaperSearchHistoryMood[] = "mood";
+const char kWallpaperSearchHistoryStyle[] = "style";
+const char kWallpaperSearchHistorySubject[] = "subject";
 
 using testing::Return;
 using testing::SaveArg;
@@ -112,17 +115,25 @@
 TEST_F(WallpaperSearchBackgroundManagerTest, GetHistory) {
   // Fill history pref.
   base::Value::List history = base::Value::List();
-  std::vector<base::Token> tokens;
+  std::vector<HistoryEntry> history_entries;
   for (int i = 0; i < 3; i++) {
     base::Token temp_token = base::Token::CreateRandom();
-    tokens.push_back(temp_token);
-    history.Append(base::Value::Dict().Set(kWallpaperSearchHistoryId,
-                                           temp_token.ToString()));
+    HistoryEntry entry = HistoryEntry(temp_token);
+    entry.subject = "foo" + base::NumberToString(i);
+    entry.mood = "bar" + base::NumberToString(i);
+    entry.style = "foobar" + base::NumberToString(i);
+    history_entries.push_back(entry);
+    history.Append(
+        base::Value::Dict()
+            .Set(kWallpaperSearchHistoryId, temp_token.ToString())
+            .Set(kWallpaperSearchHistorySubject, entry.subject.value())
+            .Set(kWallpaperSearchHistoryMood, entry.mood.value())
+            .Set(kWallpaperSearchHistoryStyle, entry.style.value()));
   }
   pref_service().SetList(prefs::kNtpWallpaperSearchHistory, std::move(history));
 
   auto result = wallpaper_search_background_manager().GetHistory();
-  EXPECT_EQ(result, tokens);
+  EXPECT_EQ(result, history_entries);
 }
 
 TEST_F(WallpaperSearchBackgroundManagerTest, SetHistoryImage) {
@@ -235,18 +246,31 @@
   ON_CALL(mock_ntp_custom_background_service(), GetCustomBackground())
       .WillByDefault(Return(absl::make_optional(custom_background)));
 
-  wallpaper_search_background_manager().SaveCurrentBackgroundToHistory(
-      HistoryEntry(token));
+  HistoryEntry entry = HistoryEntry(token);
+  entry.subject = "foo";
+  entry.mood = "bar";
+  entry.style = "foobar";
+  wallpaper_search_background_manager().SaveCurrentBackgroundToHistory(entry);
   task_environment().RunUntilIdle();
 
   const base::Value::List& history =
       pref_service().GetList(prefs::kNtpWallpaperSearchHistory);
   ASSERT_EQ(history.size(), 1u);
   ASSERT_TRUE(history.front().is_dict());
-  const base::Value* id =
-      history.front().GetDict().Find(kWallpaperSearchHistoryId);
+  const base::Value::Dict& history_dict = history.front().GetDict();
+  const base::Value* id = history_dict.Find(kWallpaperSearchHistoryId);
+  const base::Value* subject =
+      history_dict.Find(kWallpaperSearchHistorySubject);
+  const base::Value* mood = history_dict.Find(kWallpaperSearchHistoryMood);
+  const base::Value* style = history_dict.Find(kWallpaperSearchHistoryStyle);
   ASSERT_TRUE(id->is_string());
+  ASSERT_TRUE(subject->is_string());
+  ASSERT_TRUE(mood->is_string());
+  ASSERT_TRUE(style->is_string());
   EXPECT_EQ(token.ToString(), id->GetString());
+  EXPECT_EQ(entry.subject, subject->GetString());
+  EXPECT_EQ(entry.mood, mood->GetString());
+  EXPECT_EQ(entry.style, style->GetString());
 }
 
 // Test that the last history entry is deleted when a new entry is added,
@@ -272,8 +296,10 @@
   ON_CALL(mock_ntp_custom_background_service(), GetCustomBackground())
       .WillByDefault(Return(absl::make_optional(custom_background)));
 
-  wallpaper_search_background_manager().SaveCurrentBackgroundToHistory(
-      HistoryEntry(theme_token));
+  HistoryEntry entry = HistoryEntry(theme_token);
+  entry.subject = "foo";
+  entry.mood = "bar";
+  wallpaper_search_background_manager().SaveCurrentBackgroundToHistory(entry);
   task_environment().RunUntilIdle();
 
   // Check that the history is still 6 long and the first entry is the new one.
@@ -281,10 +307,19 @@
       pref_service().GetList(prefs::kNtpWallpaperSearchHistory);
   ASSERT_EQ(new_history.size(), 6u);
   ASSERT_TRUE(new_history.front().is_dict());
-  const base::Value* id =
-      new_history.front().GetDict().Find(kWallpaperSearchHistoryId);
+  const base::Value::Dict& history_dict = new_history.front().GetDict();
+  const base::Value* id = history_dict.Find(kWallpaperSearchHistoryId);
+  const base::Value* subject =
+      history_dict.Find(kWallpaperSearchHistorySubject);
+  const base::Value* mood = history_dict.Find(kWallpaperSearchHistoryMood);
+  const base::Value* style = history_dict.Find(kWallpaperSearchHistoryStyle);
   ASSERT_TRUE(id->is_string());
+  ASSERT_TRUE(subject->is_string());
+  ASSERT_TRUE(mood->is_string());
+  EXPECT_EQ(style, nullptr);
   EXPECT_EQ(theme_token.ToString(), id->GetString());
+  EXPECT_EQ(entry.subject, subject->GetString());
+  EXPECT_EQ(entry.mood, mood->GetString());
 
   // Check that the file for deleted history entry has been deleted and the
   // rest are still there.
@@ -304,8 +339,10 @@
   for (int i = 0; i < 6; ++i) {
     base::Token temp_token = base::Token::CreateRandom();
     tokens.push_back(temp_token);
-    history.Append(base::Value::Dict().Set(kWallpaperSearchHistoryId,
-                                           temp_token.ToString()));
+    history.Append(base::Value::Dict()
+                       .Set(kWallpaperSearchHistoryId, temp_token.ToString())
+                       .Set(kWallpaperSearchHistorySubject,
+                            "foo" + base::NumberToString(i)));
     base::WriteFile(GetFilePathForBackground(temp_token), "hi");
   }
   pref_service().SetList(prefs::kNtpWallpaperSearchHistory, std::move(history));
@@ -317,8 +354,10 @@
   ON_CALL(mock_ntp_custom_background_service(), GetCustomBackground())
       .WillByDefault(Return(absl::make_optional(custom_background)));
 
+  HistoryEntry theme_entry = HistoryEntry(theme_token);
+  theme_entry.subject = "foo2";
   wallpaper_search_background_manager().SaveCurrentBackgroundToHistory(
-      HistoryEntry(theme_token));
+      theme_entry);
   task_environment().RunUntilIdle();
 
   // Check that the history is still 6 long and in the correct order.
@@ -328,20 +367,40 @@
       pref_service().GetList(prefs::kNtpWallpaperSearchHistory);
   ASSERT_EQ(new_history.size(), 6u);
   ASSERT_TRUE(new_history.front().is_dict());
+  const base::Value::Dict& first_history_dict = new_history.front().GetDict();
   const base::Value* first_id =
-      new_history.front().GetDict().Find(kWallpaperSearchHistoryId);
+      first_history_dict.Find(kWallpaperSearchHistoryId);
+  const base::Value* first_subject =
+      first_history_dict.Find(kWallpaperSearchHistorySubject);
+  const base::Value* first_mood =
+      first_history_dict.Find(kWallpaperSearchHistoryMood);
+  const base::Value* first_style =
+      first_history_dict.Find(kWallpaperSearchHistoryStyle);
   ASSERT_TRUE(first_id->is_string());
+  ASSERT_TRUE(first_subject->is_string());
+  EXPECT_EQ(first_mood, nullptr);
+  EXPECT_EQ(first_style, nullptr);
   EXPECT_EQ(theme_token.ToString(), first_id->GetString());
+  EXPECT_EQ(theme_entry.subject, first_subject->GetString());
   bool before_entry_pos = true;
   for (int i = 1; i < 6; ++i) {
     // If we haven't hit where |theme_token| used to be in the history,
     // the entry we are looking at will be one index back in |tokens| vs
     // |new_history|.
     ASSERT_TRUE(new_history[i].is_dict());
-    const base::Value* id =
-        new_history[i].GetDict().Find(kWallpaperSearchHistoryId);
+    const base::Value::Dict& history_dict = new_history[i].GetDict();
+    const base::Value* id = history_dict.Find(kWallpaperSearchHistoryId);
+    const base::Value* subject =
+        history_dict.Find(kWallpaperSearchHistorySubject);
+    const base::Value* mood = history_dict.Find(kWallpaperSearchHistoryMood);
+    const base::Value* style = history_dict.Find(kWallpaperSearchHistoryStyle);
     ASSERT_TRUE(id->is_string());
+    ASSERT_TRUE(subject->is_string());
+    EXPECT_EQ(mood, nullptr);
+    EXPECT_EQ(style, nullptr);
     EXPECT_EQ(id->GetString(), tokens[before_entry_pos ? i - 1 : i].ToString());
+    EXPECT_EQ("foo" + base::NumberToString(before_entry_pos ? i - 1 : i),
+              subject->GetString());
 
     if (tokens[i].ToString() == theme_token.ToString()) {
       before_entry_pos = false;
diff --git a/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.cc b/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.cc
index 4eb5e8e..8651f57 100644
--- a/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.cc
+++ b/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.cc
@@ -6,6 +6,7 @@
 
 #include "base/token.h"
 
+HistoryEntry::HistoryEntry() = default;
 HistoryEntry::HistoryEntry(const base::Token& id) : id(id) {}
 HistoryEntry::HistoryEntry(const HistoryEntry&) = default;
 HistoryEntry::HistoryEntry(HistoryEntry&&) = default;
@@ -13,3 +14,8 @@
 
 HistoryEntry& HistoryEntry::operator=(const HistoryEntry&) = default;
 HistoryEntry& HistoryEntry::operator=(HistoryEntry&&) = default;
+bool HistoryEntry::operator==(const HistoryEntry& rhs) const {
+  return this->id.ToString() == rhs.id.ToString() &&
+         this->subject == rhs.subject && this->style == rhs.style &&
+         this->mood == rhs.mood;
+}
diff --git a/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.h b/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.h
index 95d0f41..433f4b2 100644
--- a/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.h
+++ b/chrome/browser/search/background/wallpaper_search/wallpaper_search_data.h
@@ -11,7 +11,7 @@
 
 // Represents a history entry in wallpaper search.
 struct HistoryEntry {
-  HistoryEntry() = default;
+  HistoryEntry();
   explicit HistoryEntry(const base::Token& id);
   HistoryEntry(const HistoryEntry&);
   HistoryEntry(HistoryEntry&&);
@@ -19,8 +19,12 @@
 
   HistoryEntry& operator=(const HistoryEntry&);
   HistoryEntry& operator=(HistoryEntry&&);
+  bool operator==(const HistoryEntry& rhs) const;
 
   base::Token id;
+  absl::optional<std::string> subject;
+  absl::optional<std::string> style;
+  absl::optional<std::string> mood;
 };
 
 #endif  // CHROME_BROWSER_SEARCH_BACKGROUND_WALLPAPER_SEARCH_WALLPAPER_SEARCH_DATA_H_
diff --git a/chrome/browser/tpcd/experiment/BUILD.gn b/chrome/browser/tpcd/experiment/BUILD.gn
index 9edb87c..4d60186b3 100644
--- a/chrome/browser/tpcd/experiment/BUILD.gn
+++ b/chrome/browser/tpcd/experiment/BUILD.gn
@@ -2,6 +2,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/chromeos/ui_mode.gni")
+
 source_set("unit_tests") {
   testonly = true
   sources = [
@@ -20,4 +22,8 @@
     "//content/test:test_support",
     "//testing/gtest",
   ]
+
+  if (is_chromeos_ash) {
+    deps += [ "//chromeos/ash/components/browser_context_helper" ]
+  }
 }
diff --git a/chrome/browser/tpcd/experiment/experiment_manager_impl.cc b/chrome/browser/tpcd/experiment/experiment_manager_impl.cc
index 471a555..f6ad212 100644
--- a/chrome/browser/tpcd/experiment/experiment_manager_impl.cc
+++ b/chrome/browser/tpcd/experiment/experiment_manager_impl.cc
@@ -16,6 +16,8 @@
 #include "base/no_destructor.h"
 #include "base/sequence_checker.h"
 #include "base/task/single_thread_task_runner.h"
+#include "build/buildflag.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/profiles/profile.h"
@@ -28,6 +30,10 @@
 #include "content/public/common/content_features.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chrome/browser/profiles/profile_types_ash.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace tpcd::experiment {
 namespace {
 
@@ -58,6 +64,14 @@
     return nullptr;
   }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  // Ash internal profile should not be accounted for the experiment
+  // eligibility, and therefore should not create the experiment manager.
+  if (!IsUserProfile(profile)) {
+    return nullptr;
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
   if (!features::kCookieDeprecationFacilitatedTestingEnableOTRProfiles.Get() &&
       (profile->IsOffTheRecord() || profile->IsGuestSession())) {
     return nullptr;
diff --git a/chrome/browser/tpcd/experiment/experiment_manager_impl_unittest.cc b/chrome/browser/tpcd/experiment/experiment_manager_impl_unittest.cc
index 879cc128..9e42f7e 100644
--- a/chrome/browser/tpcd/experiment/experiment_manager_impl_unittest.cc
+++ b/chrome/browser/tpcd/experiment/experiment_manager_impl_unittest.cc
@@ -7,18 +7,25 @@
 #include "base/test/bind.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
-#include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "build/buildflag.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/tpcd/experiment/tpcd_experiment_features.h"
 #include "chrome/browser/tpcd/experiment/tpcd_pref_names.h"
 #include "chrome/browser/tpcd/experiment/tpcd_utils.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile_manager.h"
 #include "components/prefs/testing_pref_service.h"
 #include "content/public/common/content_features.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace tpcd::experiment {
 namespace {
 
@@ -38,12 +45,11 @@
 
 class ExperimentManagerImplTestBase : public testing::Test {
  public:
-  ExperimentManagerImplTestBase()
-      : local_state_(TestingBrowserProcess::GetGlobal()) {}
-
-  PrefService& prefs() { return *local_state_.Get(); }
+  PrefService& prefs() { return *profile_manager_.local_state()->Get(); }
 
   void SetUp() override {
+    ASSERT_TRUE(profile_manager_.SetUp());
+
     prefs().SetInteger(
         prefs::kTPCDExperimentClientState,
         static_cast<int>(utils::ExperimentState::kUnknownEligibility));
@@ -51,9 +57,9 @@
   }
 
  protected:
-  base::test::TaskEnvironment task_environment_{
+  content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  ScopedTestingLocalState local_state_;
+  TestingProfileManager profile_manager_{TestingBrowserProcess::GetGlobal()};
   base::MockCallback<ExperimentManager::EligibilityDecisionCallback>
       mock_callback_;
   base::TimeDelta delay_time_;
@@ -317,6 +323,14 @@
   EXPECT_EQ(TestingExperimentManagerImpl().IsClientEligible(), absl::nullopt);
 }
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+TEST_F(ExperimentManagerImplTest, AshInternalProfile_NotCreated) {
+  auto* internal_profile =
+      profile_manager_.CreateTestingProfile(ash::kSigninBrowserContextBaseName);
+  EXPECT_FALSE(ExperimentManagerImpl::GetForProfile(internal_profile));
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // The parameter indicates whether to disable 3pcs.
 class ExperimentManagerImplSyntheticTrialTest
     : public ExperimentManagerImplTestBase,
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 11553e8..d9ee733 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5678,6 +5678,12 @@
       "views/performance_controls/memory_saver_chip_view.h",
       "views/performance_controls/memory_saver_resource_view.cc",
       "views/performance_controls/memory_saver_resource_view.h",
+      "views/permissions/chip/permission_dashboard_controller.cc",
+      "views/permissions/chip/permission_dashboard_controller.h",
+      "views/permissions/chip/permission_dashboard_layout.cc",
+      "views/permissions/chip/permission_dashboard_layout.h",
+      "views/permissions/chip/permission_dashboard_view.cc",
+      "views/permissions/chip/permission_dashboard_view.h",
       "views/permissions/chip_controller.cc",
       "views/permissions/chip_controller.h",
       "views/permissions/chooser_bubble_ui.cc",
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.cc b/chrome/browser/ui/android/webid/account_selection_view_android.cc
index 66584597..3dfd2c3b 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.cc
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.cc
@@ -259,7 +259,6 @@
 
 content::WebContents* AccountSelectionViewAndroid::ShowModalDialog(
     const GURL& url) {
-  // TODO(crbug.com/1510449): Connect dismiss callback to closing of CCT.
   if (!MaybeCreateJavaObject()) {
     // The Java object is tied to the bottomsheet availability, so if we hadn't
     // created one and the bottomsheet is not available then the CCT will not be
diff --git a/chrome/browser/ui/side_panel/companion/companion_utils.cc b/chrome/browser/ui/side_panel/companion/companion_utils.cc
index 9b1924f..5b3a314 100644
--- a/chrome/browser/ui/side_panel/companion/companion_utils.cc
+++ b/chrome/browser/ui/side_panel/companion/companion_utils.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/side_panel/companion/companion_utils.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/companion/core/constants.h"
 #include "chrome/browser/companion/core/features.h"
 #include "chrome/browser/companion/core/utils.h"
@@ -21,7 +22,7 @@
 namespace companion {
 
 bool IsCompanionFeatureEnabled() {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS)
   if (!base::FeatureList::IsEnabled(lens::features::kLensStandalone)) {
     return false;
   }
@@ -33,7 +34,7 @@
              features::internal::kCompanionEnabledByObservingExpsNavigations);
 #else
   return false;
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
+#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING) && !BUILDFLAG(IS_CHROMEOS)
 }
 
 bool IsCompanionAvailableForCurrentActiveTab(const Browser* browser) {
diff --git a/chrome/browser/ui/views/bubble_anchor_util_views.cc b/chrome/browser/ui/views/bubble_anchor_util_views.cc
index 84a4399..7932fe5 100644
--- a/chrome/browser/ui/views/bubble_anchor_util_views.cc
+++ b/chrome/browser/ui/views/bubble_anchor_util_views.cc
@@ -11,7 +11,7 @@
 #include "chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h"
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#include "components/permissions/features.h"
+#include "components/content_settings/core/common/features.h"
 #include "ui/views/bubble/bubble_border.h"
 
 // This file contains the bubble_anchor_util implementation for a Views
@@ -22,21 +22,37 @@
 AnchorConfiguration GetPageInfoAnchorConfiguration(Browser* browser,
                                                    Anchor anchor) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-  if (anchor == kLocationBar &&
-      browser_view->GetLocationBarView()->chip_controller() &&
-      browser_view->GetLocationBarView()
-          ->chip_controller()
-          ->chip()
-          ->GetVisible()) {
-    return {browser_view->GetLocationBarView()->chip_controller()->chip(),
-            browser_view->GetLocationBarView()->chip_controller()->chip(),
-            views::BubbleBorder::TOP_LEFT};
+  auto* location_bar_view = browser_view->GetLocationBarView();
+
+  if (base::FeatureList::IsEnabled(
+          content_settings::features::kLeftHandSideActivityIndicators)) {
+    auto* permission_dashboard_view =
+        location_bar_view->permission_dashboard_controller()
+            ->permission_dashboard_view();
+
+    if (anchor == kLocationBar && permission_dashboard_view->GetVisible()) {
+      if (permission_dashboard_view->GetIndicatorChip()->GetVisible()) {
+        return {permission_dashboard_view->GetIndicatorChip(),
+                permission_dashboard_view->GetIndicatorChip(),
+                views::BubbleBorder::TOP_LEFT};
+      }
+
+      return {permission_dashboard_view->GetRequestChip(),
+              permission_dashboard_view->GetRequestChip(),
+              views::BubbleBorder::TOP_LEFT};
+    }
+  } else {
+    auto* request_chip_view = location_bar_view->GetChipController()->chip();
+    if (anchor == kLocationBar && request_chip_view->GetVisible()) {
+      return {request_chip_view, request_chip_view,
+              views::BubbleBorder::TOP_LEFT};
+    }
   }
 
-  if (anchor == kLocationBar && browser_view->GetLocationBarView()->IsDrawn())
-    return {browser_view->GetLocationBarView(),
-            browser_view->GetLocationBarView()->location_icon_view(),
+  if (anchor == kLocationBar && location_bar_view->IsDrawn()) {
+    return {location_bar_view, location_bar_view->location_icon_view(),
             views::BubbleBorder::TOP_LEFT};
+  }
 
   if (anchor == kLocationBar && browser_view->GetIsPictureInPictureType()) {
     auto* frame_view = static_cast<PictureInPictureBrowserFrameView*>(
@@ -69,12 +85,12 @@
 AnchorConfiguration GetPermissionPromptBubbleAnchorConfiguration(
     Browser* browser) {
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-  if (browser_view->GetLocationBarView()->chip_controller() &&
+  if (browser_view->GetLocationBarView()->GetChipController() &&
       browser_view->GetLocationBarView()
-          ->chip_controller()
+          ->GetChipController()
           ->IsPermissionPromptChipVisible()) {
     return {browser_view->GetLocationBarView(),
-            browser_view->GetLocationBarView()->chip_controller()->chip(),
+            browser_view->GetLocationBarView()->GetChipController()->chip(),
             views::BubbleBorder::TOP_LEFT};
   }
   return GetPageInfoAnchorConfiguration(browser);
diff --git a/chrome/browser/ui/views/chrome_layout_provider.cc b/chrome/browser/ui/views/chrome_layout_provider.cc
index 7c1b718..1b49700 100644
--- a/chrome/browser/ui/views/chrome_layout_provider.cc
+++ b/chrome/browser/ui/views/chrome_layout_provider.cc
@@ -166,6 +166,8 @@
       return 24;
     case DISTANCE_OMNIBOX_CELL_VERTICAL_PADDING:
       return OmniboxFieldTrial::IsCr23LayoutEnabled() ? 12 : 8;
+    case DISTANCE_OMNIBOX_CHIPS_OVERLAP:
+      return 10;
     case DISTANCE_OMNIBOX_TWO_LINE_CELL_VERTICAL_PADDING:
       return 4;
     case DISTANCE_SIDE_PANEL_HEADER_VECTOR_ICON_SIZE:
diff --git a/chrome/browser/ui/views/chrome_layout_provider.h b/chrome/browser/ui/views/chrome_layout_provider.h
index 99d7c68..259be6f 100644
--- a/chrome/browser/ui/views/chrome_layout_provider.h
+++ b/chrome/browser/ui/views/chrome_layout_provider.h
@@ -89,6 +89,9 @@
   DISTANCE_BETWEEN_PRIMARY_AND_SECONDARY_LABELS_HORIZONTAL,
   // Vertical padding at the top and bottom of the an omnibox match row.
   DISTANCE_OMNIBOX_CELL_VERTICAL_PADDING,
+  // Horizontal overlap between an activity indicator chip and a permission
+  // request chip.
+  DISTANCE_OMNIBOX_CHIPS_OVERLAP,
   // Vertical padding at the top and bottom of the an omnibox match row for two
   // line layout.
   DISTANCE_OMNIBOX_TWO_LINE_CELL_VERTICAL_PADDING,
diff --git a/chrome/browser/ui/views/chrome_views_delegate.h b/chrome/browser/ui/views/chrome_views_delegate.h
index 2d08ec1..381ccad 100644
--- a/chrome/browser/ui/views/chrome_views_delegate.h
+++ b/chrome/browser/ui/views/chrome_views_delegate.h
@@ -42,6 +42,11 @@
   bool ShouldCloseMenuIfMouseCaptureLost() const override;
 #endif
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  bool ShouldWindowHaveRoundedCorners(
+      const gfx::NativeWindow window) const override;
+#endif
+
 #if BUILDFLAG(IS_CHROMEOS)
   std::unique_ptr<views::NonClientFrameView> CreateDefaultNonClientFrameView(
       views::Widget* widget) override;
diff --git a/chrome/browser/ui/views/chrome_views_delegate_lacros.cc b/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
index 4c6eb95..38181b7d 100644
--- a/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
+++ b/chrome/browser/ui/views/chrome_views_delegate_lacros.cc
@@ -6,9 +6,16 @@
 
 #include <memory>
 
+#include "chromeos/ui/frame/frame_utils.h"
 #include "chromeos/ui/frame/non_client_frame_view_base.h"
+#include "ui/gfx/native_widget_types.h"
 
 std::unique_ptr<views::NonClientFrameView>
 ChromeViewsDelegate::CreateDefaultNonClientFrameView(views::Widget* widget) {
   return std::make_unique<chromeos::NonClientFrameViewBase>(widget);
 }
+
+bool ChromeViewsDelegate::ShouldWindowHaveRoundedCorners(
+    gfx::NativeWindow window) const {
+  return chromeos::ShouldWindowHaveRoundedCorners(window);
+}
diff --git a/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc b/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
index 6177563a..f97c1da 100644
--- a/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup_interactive_uitest.cc
@@ -114,9 +114,9 @@
   // If so, click on the chip to open the bubble.
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
   LocationBarView* lbv = browser_view->toolbar()->location_bar();
-  if (lbv->chip_controller()->IsPermissionPromptChipVisible() &&
-      !lbv->chip_controller()->IsBubbleShowing()) {
-    views::test::ButtonTestApi(lbv->chip_controller()->chip())
+  if (lbv->GetChipController()->IsPermissionPromptChipVisible() &&
+      !lbv->GetChipController()->IsBubbleShowing()) {
+    views::test::ButtonTestApi(lbv->GetChipController()->chip())
         .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
                                     gfx::Point(), ui::EventTimeForNow(),
                                     ui::EF_LEFT_MOUSE_BUTTON, 0));
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.cc
index 0e17f89..8837b8c 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.cc
@@ -9,7 +9,6 @@
 #include "base/check.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_frame.h"
-#include "chrome/browser/ui/views/frame/browser_non_client_frame_view.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_lacros.h"
 #include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
@@ -17,10 +16,6 @@
 #include "chromeos/ui/base/window_properties.h"
 #include "chromeos/ui/frame/frame_utils.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom-shared.h"
-#include "ui/compositor/layer.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/gfx/geometry/rounded_corners_f.h"
-#include "ui/gfx/geometry/rrect_f.h"
 #include "ui/platform_window/extensions/wayland_extension.h"
 #include "ui/platform_window/platform_window.h"
 
@@ -62,97 +57,6 @@
   native_frame_->set_host(nullptr);
 }
 
-void BrowserDesktopWindowTreeHostLacros::UpdateFrameHints() {
-  const float scale = device_scale_factor();
-  const gfx::Size widget_size_px =
-      platform_window()->GetBoundsInPixels().size();
-
-  auto* wayland_extension = ui::GetWaylandExtension(*platform_window());
-  DCHECK(wayland_extension);
-
-  const gfx::RoundedCornersF window_radii =
-      wayland_extension->GetWindowCornersRadii();
-
-  std::vector<gfx::Rect> opaque_region;
-
-  const aura::Window* native_window = browser_view_->GetNativeWindow();
-  const bool should_have_rounded_corners =
-      chromeos::ShouldWindowHaveRoundedCorners(native_window);
-  if (should_have_rounded_corners) {
-    GetContentWindow()->layer()->SetRoundedCornerRadius(window_radii);
-    GetContentWindow()->layer()->SetIsFastRoundedCorner(true);
-
-    // The opaque region is a list of rectangles that contain only fully
-    // opaque pixels of the window.  We need to convert the clipping
-    // rounded-rect into this format.
-
-    const BrowserNonClientFrameView* view =
-        browser_view_->frame()->GetFrameView();
-    gfx::Rect local_bounds = view->GetLocalBounds();
-    gfx::RRectF rounded_corners_rect(gfx::RectF(local_bounds), window_radii);
-    gfx::RectF rect_f = rounded_corners_rect.rect();
-    rect_f.Scale(scale);
-
-    // It is acceptable to omit some pixels that are opaque, but the region
-    // must not include any translucent pixels.  Therefore, we must
-    // conservatively scale to the enclosed rectangle.
-    gfx::Rect rect = gfx::ToEnclosedRect(rect_f);
-
-    // Create the initial region from the clipping rectangle without rounded
-    // corners.
-    cc::Region region(rect);
-
-    // Now subtract out the small rectangles that cover the corners.
-    struct {
-      gfx::RRectF::Corner corner;
-      bool left;
-      bool upper;
-    } kCorners[] = {
-        {gfx::RRectF::Corner::kUpperLeft, true, true},
-        {gfx::RRectF::Corner::kUpperRight, false, true},
-        {gfx::RRectF::Corner::kLowerLeft, true, false},
-        {gfx::RRectF::Corner::kLowerRight, false, false},
-    };
-    for (const auto& corner : kCorners) {
-      auto corner_radii = rounded_corners_rect.GetCornerRadii(corner.corner);
-      auto rx = std::ceil(scale * corner_radii.x());
-      auto ry = std::ceil(scale * corner_radii.y());
-      auto corner_rect =
-          gfx::Rect(corner.left ? rect.x() : rect.right() - rx,
-                    corner.upper ? rect.y() : rect.bottom() - ry, rx, ry);
-      region.Subtract(corner_rect);
-    }
-
-    // Convert the region to a list of rectangles.
-    for (gfx::Rect i : region) {
-      opaque_region.push_back(i);
-    }
-  } else {
-    GetContentWindow()->layer()->SetRoundedCornerRadius({});
-    GetContentWindow()->layer()->SetIsFastRoundedCorner(false);
-    opaque_region.push_back({{}, widget_size_px});
-  }
-  // TODO(crbug.com/1306688): Instead of setting OpaqueRegion, set the rounded
-  // corners in dp.
-  platform_window()->SetOpaqueRegion(opaque_region);
-
-  // If the window is rounded, we hint the platform to match the drop shadow's
-  // radii to the window's radii. Otherwise, we allow the platform to
-  // determine the drop shadow's radii.
-  if (should_have_rounded_corners) {
-    wayland_extension->SetShadowCornersRadii(window_radii);
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// BrowserDesktopWindowTreeHostLacros,
-//     DesktopWindowTreeHost implementation:
-
-void BrowserDesktopWindowTreeHostLacros::OnWidgetInitDone() {
-  DesktopWindowTreeHostLacros::OnWidgetInitDone();
-
-  UpdateFrameHints();
-}
 
 ////////////////////////////////////////////////////////////////////////////////
 // BrowserDesktopWindowTreeHostLacros,
@@ -211,13 +115,6 @@
   }
 }
 
-void BrowserDesktopWindowTreeHostLacros::OnBoundsChanged(
-    const BoundsChange& change) {
-  DesktopWindowTreeHostLacros::OnBoundsChanged(change);
-
-  UpdateFrameHints();
-}
-
 void BrowserDesktopWindowTreeHostLacros::OnWindowStateChanged(
     ui::PlatformWindowState old_window_show_state,
     ui::PlatformWindowState new_window_show_state) {
@@ -232,8 +129,6 @@
     // BrowserView::ProcessFullscreen will no-op, so this call is harmless.
     browser_view_->FullscreenStateChanging();
   }
-
-  UpdateFrameHints();
 }
 
 void BrowserDesktopWindowTreeHostLacros::OnFullscreenTypeChanged(
@@ -246,18 +141,6 @@
   browser_view_->FullscreenStateChanged();
 }
 
-void BrowserDesktopWindowTreeHostLacros::OnOverviewModeChanged(
-    bool in_overview) {
-  DesktopWindowTreeHostLacros::OnOverviewModeChanged(in_overview);
-
-  // Window corner radius depends on weather the window is in overview mode or
-  // not. Once the overview property has been updated, the browser window
-  // corners needs to be updated.
-  // See `chromeos::GetFrameCornerRadius()` for more details.
-  // TODO(b/301501363): Rename to UpdateWindowHints.
-  UpdateFrameHints();
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // BrowserDesktopWindowTreeHostLacros,
 //     DesktopWindowTreeHostPlatform implementation:
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.h b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.h
index de8d94b3..c571d2e7 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.h
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_lacros.h
@@ -37,12 +37,6 @@
   void TabDraggingKindChanged(TabDragKind tab_drag_kind);
 
  private:
-  // Sets hints for the WM/compositor that reflect the rounded corners.
-  void UpdateFrameHints();
-
-  // DesktopWindowTreeHost:
-  void OnWidgetInitDone() override;
-
   // BrowserDesktopWindowTreeHost:
   DesktopWindowTreeHost* AsDesktopWindowTreeHost() override;
   int GetMinimizeButtonOffset() const override;
@@ -56,12 +50,10 @@
   void UnlockMouse(aura::Window* window) override;
 
   // ui::PlatformWindowDelegate
-  void OnBoundsChanged(const BoundsChange& change) override;
   void OnWindowStateChanged(ui::PlatformWindowState old_state,
                             ui::PlatformWindowState new_state) override;
   void OnFullscreenTypeChanged(ui::PlatformFullscreenType old_type,
                                ui::PlatformFullscreenType new_type) override;
-  void OnOverviewModeChanged(bool in_overview) override;
 
   const raw_ptr<BrowserView> browser_view_;
   raw_ptr<DesktopBrowserFrameLacros> native_frame_ = nullptr;
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
index a0d2a65..bf788b2 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
@@ -179,7 +179,7 @@
 
 bool BrowserDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const {
   return platform_window()->CanSetDecorationInsets() &&
-         platform_window()->IsTranslucentWindowOpacitySupported();
+         views::Widget::IsWindowCompositingSupported();
 }
 
 void BrowserDesktopWindowTreeHostLinux::UpdateFrameHints() {
@@ -216,7 +216,7 @@
         showing_frame ? std::optional<gfx::Rect>(input_bounds) : std::nullopt);
   }
 
-  if (window->IsTranslucentWindowOpacitySupported()) {
+  if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) {
     // Set the opaque region.
     std::vector<gfx::Rect> opaque_region;
     if (showing_frame) {
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_native_unittest.cc b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_native_unittest.cc
index 3bbdbe9..c64354b 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_native_unittest.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_native_unittest.cc
@@ -79,7 +79,6 @@
   bool EverHasVisibleBackgroundTabShapes() const override { return false; }
   void UpdateWindowControlsOverlay(
       const gfx::Rect& bounding_rect) const override {}
-  bool IsTranslucentWindowOpacitySupported() const override { return true; }
   bool ShouldDrawRestoredFrameShadow() const override { return true; }
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   ui::WindowTiledEdges GetTiledEdges() const override { return {}; }
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_unittest.cc b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_unittest.cc
index 423dba0..15b1229 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_unittest.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_layout_linux_unittest.cc
@@ -46,7 +46,6 @@
   bool EverHasVisibleBackgroundTabShapes() const override { return false; }
   void UpdateWindowControlsOverlay(
       const gfx::Rect& bounding_rect) const override {}
-  bool IsTranslucentWindowOpacitySupported() const override { return true; }
   bool ShouldDrawRestoredFrameShadow() const override { return true; }
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   ui::WindowTiledEdges GetTiledEdges() const override { return tiled_edges_; }
diff --git a/chrome/browser/ui/views/frame/browser_frame_view_linux.cc b/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
index 4bbafb7..bbdb7b8b 100644
--- a/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_view_linux.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
 #include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/linux/linux_ui.h"
+#include "ui/ozone/public/ozone_platform.h"
 #include "ui/views/layout/layout_provider.h"
 #include "ui/views/window/window_button_order_provider.h"
 
@@ -95,7 +96,8 @@
 }
 
 float BrowserFrameViewLinux::GetRestoredCornerRadiusDip() const {
-  if (!UseCustomFrame() || !IsTranslucentWindowOpacitySupported()) {
+  if (!UseCustomFrame() ||
+      !ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) {
     return 0;
   }
   return ChromeLayoutProvider::Get()->GetCornerRadiusMetric(
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index a0d0eaf..255a99a 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -187,6 +187,7 @@
 #include "chrome/grit/theme_resources.h"
 #include "chromeos/components/mgs/managed_guest_session_utils.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/content_settings/core/common/features.h"
 #include "components/feature_engagement/public/event_constants.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/feature_engagement/public/tracker.h"
@@ -3518,9 +3519,9 @@
 
   // Tab has a pending permission request.
   if (toolbar_ && toolbar_->location_bar() &&
-      toolbar_->location_bar()->chip_controller() &&
+      toolbar_->location_bar()->GetChipController() &&
       toolbar_->location_bar()
-          ->chip_controller()
+          ->GetChipController()
           ->IsPermissionPromptChipVisible()) {
     return l10n_util::GetStringFUTF16(
         IDS_TAB_AX_LABEL_PERMISSION_REQUESTED_FORMAT, title);
@@ -4072,15 +4073,28 @@
   if (webui_tab_strip_)
     panes->push_back(webui_tab_strip_);
 #endif
-  // When permission is requested, permission chip must be first pane in the
-  // pane traversal order to be easily accessible for keyboard users.
-  if (toolbar_ && toolbar_->location_bar() &&
-      toolbar_->location_bar()->chip_controller() &&
-      toolbar_->location_bar()
-          ->chip_controller()
-          ->IsPermissionPromptChipVisible()) {
-    panes->push_back(toolbar_->location_bar()->chip_controller()->chip());
+  // If activity indicators or a permission request chip is visible, it must be
+  // in the first position in the pane traversal order to be easily accessible
+  // for keyboard users.
+  if (base::FeatureList::IsEnabled(
+          content_settings::features::kLeftHandSideActivityIndicators)) {
+    if (toolbar_ && toolbar_->location_bar() &&
+        toolbar_->location_bar()
+            ->permission_dashboard_controller()
+            ->permission_dashboard_view()
+            ->GetVisible()) {
+      panes->push_back(toolbar_->location_bar()
+                           ->permission_dashboard_controller()
+                           ->permission_dashboard_view());
+    }
+  } else if (toolbar_ && toolbar_->location_bar() &&
+             toolbar_->location_bar()->GetChipController() &&
+             toolbar_->location_bar()
+                 ->GetChipController()
+                 ->IsPermissionPromptChipVisible()) {
+    panes->push_back(toolbar_->location_bar()->GetChipController()->chip());
   }
+
   panes->push_back(toolbar_button_provider_->GetAsAccessiblePaneView());
   if (tab_strip_region_view_)
     panes->push_back(tab_strip_region_view_);
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
index 1f72952..8d3fcc4 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -581,10 +581,6 @@
   }
 }
 
-bool OpaqueBrowserFrameView::IsTranslucentWindowOpacitySupported() const {
-  return frame()->IsTranslucentWindowOpacitySupported();
-}
-
 bool OpaqueBrowserFrameView::ShouldDrawRestoredFrameShadow() const {
   return false;
 }
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
index c8e89e6..63cddfec 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.h
@@ -114,7 +114,6 @@
   FrameButtonStyle GetFrameButtonStyle() const override;
   void UpdateWindowControlsOverlay(
       const gfx::Rect& bounding_rect) const override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   bool ShouldDrawRestoredFrameShadow() const override;
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   ui::WindowTiledEdges GetTiledEdges() const override;
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
index 8a62abb..08c01550 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_delegate.h
@@ -91,9 +91,6 @@
   virtual void UpdateWindowControlsOverlay(
       const gfx::Rect& bounding_rect) const = 0;
 
-  // Returns true if the system compositor supports translucent windows.
-  virtual bool IsTranslucentWindowOpacitySupported() const = 0;
-
   // Returns true if a client-side shadow should be drawn for restored windows.
   virtual bool ShouldDrawRestoredFrameShadow() const = 0;
 
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
index fd9490e..d4299a3 100644
--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
+++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout_unittest.cc
@@ -90,7 +90,6 @@
   bool EverHasVisibleBackgroundTabShapes() const override { return false; }
   void UpdateWindowControlsOverlay(
       const gfx::Rect& bounding_rect) const override {}
-  bool IsTranslucentWindowOpacitySupported() const override { return true; }
   bool ShouldDrawRestoredFrameShadow() const override { return true; }
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
   ui::WindowTiledEdges GetTiledEdges() const override { return {}; }
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 7444fd9d..be7f16ec 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -69,6 +69,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_icon_params.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
 #include "chrome/browser/ui/views/passwords/manage_passwords_icon_views.h"
+#include "chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h"
 #include "chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_icon_view.h"
 #include "chrome/browser/ui/views/sharing_hub/sharing_hub_icon_view.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
@@ -223,7 +224,20 @@
   SetPaintToLayer();
   layer()->SetFillsBoundsOpaquely(false);
 
-  CreateChip();
+  if (base::FeatureList::IsEnabled(
+          content_settings::features::kLeftHandSideActivityIndicators)) {
+    permission_dashboard_view_ =
+        AddChildViewAt(std::make_unique<PermissionDashboardView>(), 0);
+
+    permission_dashboard_controller_ =
+        std::make_unique<PermissionDashboardController>(
+            browser_, this, permission_dashboard_view_);
+  } else {
+    chip_controller_ = std::make_unique<ChipController>(
+        browser_, AddChildViewAt(std::make_unique<OmniboxChipButton>(
+                                     OmniboxChipButton::PressedCallback()),
+                                 0));
+  }
 
   const auto& typography_provider = views::TypographyProvider::Get();
   const gfx::FontList& font_list = typography_provider.GetFont(
@@ -616,8 +630,12 @@
                              !ShouldShowKeywordBubble();
 
   const bool show_overriding_permission_chip =
-      chip_controller_ && chip_controller_->chip()->GetVisible() &&
-      !ShouldShowKeywordBubble();
+      base::FeatureList::IsEnabled(
+          content_settings::features::kLeftHandSideActivityIndicators)
+          ? permission_dashboard_view_->GetVisible() &&
+                !ShouldShowKeywordBubble()
+          : chip_controller_->chip()->GetVisible() &&
+                !ShouldShowKeywordBubble();
 
   // There are 2 CR23 features that impact location bar layout. Make sure layout
   // is correct when neither, either, or both are enabled. Touch UI, whether the
@@ -713,9 +731,16 @@
   const double kLeadingDecorationMaxFraction = 0.5;
 
   if (show_overriding_permission_chip) {
-    leading_decorations.AddDecoration(vertical_padding, location_height, false,
-                                      0, /*intra_item_padding=*/0, icon_left,
-                                      chip_controller_->chip());
+    if (base::FeatureList::IsEnabled(
+            content_settings::features::kLeftHandSideActivityIndicators)) {
+      leading_decorations.AddDecoration(vertical_padding, location_height,
+                                        false, 0, /*intra_item_padding=*/0,
+                                        icon_left, permission_dashboard_view_);
+    } else {
+      leading_decorations.AddDecoration(vertical_padding, location_height,
+                                        false, 0, /*intra_item_padding=*/0,
+                                        icon_left, chip_controller_->chip());
+    }
   }
 
   if (ShouldShowKeywordBubble()) {
@@ -879,8 +904,9 @@
           ? kColorPageActionIcon
           : kColorOmniboxResultsIcon);
   page_action_icon_controller_->SetIconColor(icon_color);
-  for (ContentSettingImageView* image_view : content_setting_views_)
+  for (ContentSettingImageView* image_view : content_setting_views_) {
     image_view->SetIconColor(icon_color);
+  }
 
   RefreshBackground();
   RefreshClearAllButtonIcon();
@@ -942,19 +968,13 @@
       ->ActivateFirstInactiveBubbleForAccessibility();
 }
 
-void LocationBarView::CreateChip() {
-  DCHECK(!chip_controller_);
+ChipController* LocationBarView::GetChipController() {
+  if (base::FeatureList::IsEnabled(
+          content_settings::features::kLeftHandSideActivityIndicators)) {
+    return permission_dashboard_controller_->request_chip_controller();
+  }
 
-  if (!browser_)
-    return;
-
-  if (web_app::AppBrowserController::IsWebApp(browser_))
-    return;
-
-  chip_controller_ = std::make_unique<ChipController>(
-      browser_, AddChildViewAt(std::make_unique<OmniboxChipButton>(
-                                   OmniboxChipButton::PressedCallback()),
-                               0));
+  return chip_controller_.get();
 }
 
 void LocationBarView::UpdateWithoutTabRestore() {
@@ -1488,7 +1508,7 @@
 }
 
 bool LocationBarView::ShouldChipOverrideLocationIcon() {
-  return chip_controller_ && chip_controller_->chip()->GetVisible();
+  return GetChipController()->chip()->GetVisible();
 }
 
 bool LocationBarView::IsEditingOrEmpty() const {
@@ -1563,16 +1583,16 @@
 }
 
 void LocationBarView::RecordPageInfoMetrics() {
-  if (chip_controller_) {
+  if (GetChipController()) {
     bool confirmation_chip_collapsed_recently =
         base::TimeTicks::Now() - confirmation_chip_collapsed_time_ <=
         permissions::kConfirmationConsiderationDurationForUma;
 
-    if (!chip_controller_->chip()->GetVisible() &&
+    if (!GetChipController()->chip()->GetVisible() &&
         !confirmation_chip_collapsed_recently) {
       permissions::PermissionUmaUtil::RecordPageInfoDialogAccessType(
           permissions::PageInfoDialogAccessType::LOCK_CLICK);
-    } else if (chip_controller_->chip()->GetVisible()) {
+    } else if (GetChipController()->chip()->GetVisible()) {
       permissions::PermissionUmaUtil::RecordPageInfoDialogAccessType(
           permissions::PageInfoDialogAccessType::
               LOCK_CLICK_DURING_CONFIRMATION_CHIP);
@@ -1611,14 +1631,14 @@
 }
 
 void LocationBarView::UpdateChipVisibility() {
-  if (!chip_controller_ || !chip_controller_->chip()->GetVisible()) {
+  if (!GetChipController()->chip()->GetVisible()) {
     return;
   }
 
   if (IsEditingOrEmpty()) {
     // If a user starts typing, a permission request should be ignored and the
     // chip finalized.
-    chip_controller_->ResetPermissionPromptChip();
+    GetChipController()->ResetPermissionPromptChip();
   }
 }
 
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h
index 65ca764..face072 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.h
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.h
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/views/location_bar/content_setting_image_view.h"
 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
+#include "chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.h"
 #include "chrome/browser/ui/views/permissions/chip_controller.h"
 #include "components/permissions/permission_prompt.h"
 #include "components/security_state/core/security_state.h"
@@ -48,6 +49,7 @@
 class OmniboxViewViews;
 class PageActionIconController;
 class PageActionIconContainerView;
+class PermissionDashboardView;
 class Profile;
 class SelectedKeywordView;
 
@@ -171,11 +173,13 @@
   // accessibility.
   bool ActivateFirstInactiveBubbleForAccessibility();
 
-  // Adds chip into the LocationBarView in the first position.
-  void CreateChip();
+  // Controls the chip in the LocationBarView.
+  ChipController* GetChipController();
 
-  // Controls the chip in the LocationBarView
-  ChipController* chip_controller() { return chip_controller_.get(); }
+  // Controls the permission dashboard in the LocationBarView.
+  PermissionDashboardController* permission_dashboard_controller() {
+    return permission_dashboard_controller_.get();
+  }
 
   IntentChipButton* intent_chip() { return intent_chip_; }
 
@@ -413,6 +417,10 @@
   // permission information and requests.
   std::unique_ptr<ChipController> chip_controller_ = nullptr;
 
+  std::unique_ptr<PermissionDashboardController>
+      permission_dashboard_controller_;
+  raw_ptr<PermissionDashboardView> permission_dashboard_view_;
+
   // An icon to the left of the edit field: the HTTPS lock, blank page icon,
   // search icon, EV HTTPS bubble, etc.
   raw_ptr<LocationIconView> location_icon_view_ = nullptr;
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc
new file mode 100644
index 0000000..b5202f1
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.cc
@@ -0,0 +1,21 @@
+// Copyright 2024 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/ui/views/permissions/chip/permission_dashboard_controller.h"
+
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
+
+PermissionDashboardController::PermissionDashboardController(
+    Browser* browser,
+    LocationBarView* location_bar_view,
+    PermissionDashboardView* permission_dashboard_view)
+    : location_bar_view_(location_bar_view),
+      permission_dashboard_view_(permission_dashboard_view) {
+  request_chip_controller_ = std::make_unique<ChipController>(
+      browser, permission_dashboard_view_->GetRequestChip(),
+      permission_dashboard_view_);
+}
+
+PermissionDashboardController::~PermissionDashboardController() = default;
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.h b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.h
new file mode 100644
index 0000000..0e06d88
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_controller.h
@@ -0,0 +1,44 @@
+// Copyright 2024 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_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_CONTROLLER_H_
+#define CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_CONTROLLER_H_
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h"
+
+class Browser;
+class LocationBarView;
+class ChipController;
+
+class PermissionDashboardController {
+ public:
+  PermissionDashboardController(
+      Browser* browser,
+      LocationBarView* location_bar_view,
+      PermissionDashboardView* permission_dashboard_view);
+
+  ~PermissionDashboardController();
+  PermissionDashboardController(const PermissionDashboardController&) = delete;
+  PermissionDashboardController& operator=(
+      const PermissionDashboardController&) = delete;
+
+  ChipController* request_chip_controller() {
+    return request_chip_controller_.get();
+  }
+
+  PermissionDashboardView* permission_dashboard_view() {
+    return permission_dashboard_view_;
+  }
+
+ private:
+  raw_ptr<LocationBarView> location_bar_view_;
+  raw_ptr<PermissionDashboardView> permission_dashboard_view_;
+
+  std::unique_ptr<ChipController> request_chip_controller_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_CONTROLLER_H_
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.cc b/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.cc
new file mode 100644
index 0000000..86660bf
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.cc
@@ -0,0 +1,56 @@
+// Copyright 2024 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/ui/views/permissions/chip/permission_dashboard_layout.h"
+
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/view.h"
+
+PermissionDashboardLayout::PermissionDashboardLayout() = default;
+
+PermissionDashboardLayout::~PermissionDashboardLayout() = default;
+
+views::ProposedLayout PermissionDashboardLayout::CalculateProposedLayout(
+    const views::SizeBounds& size_bounds) const {
+  views::ProposedLayout layout;
+  int x = 0;
+
+  auto views = host_view()->children();
+  DCHECK(views.size() == 2);
+
+  views::View* indicator_chip_view = views[1];
+  if (indicator_chip_view->GetVisible()) {
+    gfx::Size preferred_size = indicator_chip_view->GetPreferredSize();
+    gfx::Rect proposed_bounds = gfx::Rect(gfx::Point(0, 0), preferred_size);
+    views::SizeBounds available_bounds = views::SizeBounds(preferred_size);
+
+    layout.child_layouts.push_back({indicator_chip_view,
+                                    indicator_chip_view->GetVisible(),
+                                    proposed_bounds, available_bounds});
+
+    // `indicator_chip_view` and `request_chip_view` overlaps to create an
+    // illusion that `request_chip_view` is placed under `indicator_chip_view`.
+    // To achieve that move `request_chip_view` x coordinate to the left by
+    // subtracting the overlap value from it.
+    x = preferred_size.width() - ChromeLayoutProvider::Get()->GetDistanceMetric(
+                                     DISTANCE_OMNIBOX_CHIPS_OVERLAP);
+  }
+
+  views::View* request_chip_view = views[0];
+  if (request_chip_view->GetVisible()) {
+    gfx::Size preferred_size = request_chip_view->GetPreferredSize();
+    gfx::Rect proposed_bounds = gfx::Rect(gfx::Point(x, 0), preferred_size);
+    views::SizeBounds available_bounds = views::SizeBounds(preferred_size);
+
+    layout.child_layouts.push_back({request_chip_view,
+                                    request_chip_view->GetVisible(),
+                                    proposed_bounds, available_bounds});
+  }
+
+  return layout;
+}
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.h b/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.h
new file mode 100644
index 0000000..2565ff4
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.h
@@ -0,0 +1,31 @@
+// Copyright 2024 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_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_LAYOUT_H_
+#define CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_LAYOUT_H_
+
+#include "ui/views/layout/layout_manager_base.h"
+#include "ui/views/layout/layout_types.h"
+#include "ui/views/layout/proposed_layout.h"
+
+// PermissionDashboardLayout is a LayoutManager that is designed to be used only
+// for `PermissionDashboardView`. The layout manager positions views in a row
+// with a small overlay so that the first view is displayed on top of the second
+// view.
+class PermissionDashboardLayout : public views::LayoutManagerBase {
+ public:
+  PermissionDashboardLayout();
+
+  PermissionDashboardLayout(const PermissionDashboardLayout&) = delete;
+  PermissionDashboardLayout& operator=(const PermissionDashboardLayout&) =
+      delete;
+
+  ~PermissionDashboardLayout() override;
+
+  // LayoutManagerBase:
+  views::ProposedLayout CalculateProposedLayout(
+      const views::SizeBounds& size_bounds) const override;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_LAYOUT_H_
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.cc b/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.cc
new file mode 100644
index 0000000..98c31f9
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.cc
@@ -0,0 +1,93 @@
+// Copyright 2024 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/ui/views/permissions/chip/permission_dashboard_view.h"
+
+#include "base/time/time.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/location_bar/omnibox_chip_button.h"
+#include "chrome/browser/ui/views/permissions/chip/permission_dashboard_layout.h"
+#include "components/vector_icons/vector_icons.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/style/platform_style.h"
+
+namespace {
+// Indicator chip and request chip have an overlap.
+int kChipOverlaps;
+}  // namespace
+
+PermissionDashboardView::PermissionDashboardView() {
+  SetVisible(false);
+
+  SetLayoutManager(std::make_unique<PermissionDashboardLayout>());
+
+  // Permission request chip should be created the first because it is displayed
+  // under all other views.
+  request_chip_ = AddChildView(std::make_unique<OmniboxChipButton>(
+      OmniboxChipButton::PressedCallback()));
+
+  // Activity indicators chip should be created the last because it is displayed
+  // above all other views.
+  indicator_chip_ = AddChildView(std::make_unique<OmniboxChipButton>(
+      OmniboxChipButton::PressedCallback()));
+
+  // It is unclear which chip will be shown first, hence hide both of them.
+  request_chip_->SetVisible(false);
+  indicator_chip_->SetVisible(false);
+
+  kChipOverlaps = ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_OMNIBOX_CHIPS_OVERLAP);
+}
+
+PermissionDashboardView::~PermissionDashboardView() = default;
+
+gfx::Size PermissionDashboardView::CalculatePreferredSize() const {
+  if (!request_chip_->GetVisible() && !indicator_chip_->GetVisible()) {
+    return gfx::Size(0, 0);
+  }
+
+  gfx::Size first_chip_size = indicator_chip_->GetPreferredSize();
+  if (!request_chip_->GetVisible()) {
+    return first_chip_size;
+  }
+
+  if (!indicator_chip_->GetVisible()) {
+    return request_chip_->GetPreferredSize();
+  }
+
+  gfx::Size second_chip_size = request_chip_->GetPreferredSize();
+  int width =
+      first_chip_size.width() + second_chip_size.width() - kChipOverlaps;
+  int height = first_chip_size.height();
+
+  return gfx::Size(width, height);
+}
+
+gfx::Size PermissionDashboardView::GetMinimumSize() const {
+  if (!request_chip_->GetVisible() && !indicator_chip_->GetVisible()) {
+    return gfx::Size(0, 0);
+  }
+
+  if (!request_chip_->GetVisible()) {
+    return indicator_chip_->GetMinimumSize();
+  }
+
+  if (!indicator_chip_->GetVisible()) {
+    return request_chip_->GetMinimumSize();
+  }
+
+  gfx::Size first_chip_size = indicator_chip_->GetMinimumSize();
+  gfx::Size second_chip_size = request_chip_->GetMinimumSize();
+  int width =
+      first_chip_size.width() + second_chip_size.width() - kChipOverlaps;
+  int height = first_chip_size.height();
+
+  return gfx::Size(width, height);
+}
+
+BEGIN_METADATA(PermissionDashboardView)
+END_METADATA
diff --git a/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h b/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h
new file mode 100644
index 0000000..074151bb
--- /dev/null
+++ b/chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h
@@ -0,0 +1,37 @@
+// Copyright 2024 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_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_VIEW_H_
+
+#include "base/memory/raw_ptr.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/views/view.h"
+
+class OmniboxChipButton;
+
+// UI component for activity indicators and chip button located in the omnibox.
+class PermissionDashboardView : public views::View {
+  METADATA_HEADER(PermissionDashboardView, views::View)
+
+ public:
+  PermissionDashboardView();
+  PermissionDashboardView(const PermissionDashboardView& button) = delete;
+  PermissionDashboardView& operator=(const PermissionDashboardView& button) =
+      delete;
+  ~PermissionDashboardView() override;
+
+  OmniboxChipButton* GetRequestChip() { return request_chip_; }
+  OmniboxChipButton* GetIndicatorChip() { return indicator_chip_; }
+
+  // views::View.
+  gfx::Size CalculatePreferredSize() const override;
+  gfx::Size GetMinimumSize() const override;
+
+ private:
+  raw_ptr<OmniboxChipButton> indicator_chip_;
+  raw_ptr<OmniboxChipButton> request_chip_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_PERMISSIONS_CHIP_PERMISSION_DASHBOARD_VIEW_H_
diff --git a/chrome/browser/ui/views/permissions/chip_controller.cc b/chrome/browser/ui/views/permissions/chip_controller.cc
index d0e951f..f49102e 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.cc
+++ b/chrome/browser/ui/views/permissions/chip_controller.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <string>
 #include <utility>
+
 #include "base/check.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
@@ -20,11 +21,13 @@
 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
 #include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
+#include "chrome/browser/ui/views/permissions/chip/permission_dashboard_view.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_view_factory.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_chip_model.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_style.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/content_settings/core/common/features.h"
 #include "components/permissions/features.h"
 #include "components/permissions/permission_prompt.h"
 #include "components/permissions/permission_request_manager.h"
@@ -76,8 +79,13 @@
   raw_ptr<BubbleOwnerDelegate, DanglingUntriaged> bubble_owner_ = nullptr;
 };
 
-ChipController::ChipController(Browser* browser, OmniboxChipButton* chip_view)
-    : chip_(chip_view), browser_(browser) {
+ChipController::ChipController(
+    Browser* browser,
+    OmniboxChipButton* chip_view,
+    PermissionDashboardView* permission_dashboard_view)
+    : browser_(browser),
+      chip_(chip_view),
+      permission_dashboard_view_(permission_dashboard_view) {
   chip_->SetVisible(false);
 }
 
@@ -235,6 +243,9 @@
   // a request chip is shown --> only once a confirmation should be displayed,
   // the chip should become visible.
   chip_->SetVisible(false);
+  if (permission_dashboard_view_) {
+    permission_dashboard_view_->SetVisible(false);
+  }
   permission_prompt_model_ =
       std::make_unique<PermissionPromptChipModel>(delegate);
 
@@ -289,6 +300,9 @@
   AnnouncePermissionRequestForAccessibility(
       permission_prompt_model_->GetAccessibilityChipText());
   chip_->SetVisible(true);
+  if (permission_dashboard_view_) {
+    permission_dashboard_view_->SetVisible(true);
+  }
 
   SyncChipWithModel();
 
@@ -413,6 +427,9 @@
   chip_->ResetAnimation();
   chip_->AnimateExpand(GetAnimationDuration(base::Milliseconds(350)));
   chip_->SetVisible(true);
+  if (permission_dashboard_view_) {
+    permission_dashboard_view_->SetVisible(true);
+  }
 }
 
 void ChipController::HandleConfirmation(
@@ -426,7 +443,10 @@
       permission_prompt_model_->CanDisplayConfirmation()) {
     is_confirmation_showing_ = true;
 
-    if (chip_->GetVisible()) {
+    // AnimateToFit isn't working for `PermissionDashboardView`.
+    if (chip_->GetVisible() &&
+        !base::FeatureList::IsEnabled(
+            content_settings::features::kLeftHandSideActivityIndicators)) {
       chip_->AnimateToFit(GetAnimationDuration(base::Milliseconds(200)));
     } else {
       // No request chip was shown, always expand independently of what contents
@@ -497,6 +517,9 @@
     return;
 
   chip_->SetVisible(false);
+  if (permission_dashboard_view_) {
+    permission_dashboard_view_->SetVisible(false);
+  }
   // When the chip visibility changed from visible -> hidden, the locationbar
   // layout should be updated.
   GetLocationBarView()->InvalidateLayout();
diff --git a/chrome/browser/ui/views/permissions/chip_controller.h b/chrome/browser/ui/views/permissions/chip_controller.h
index 8841d236f..6cc0f4c 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.h
+++ b/chrome/browser/ui/views/permissions/chip_controller.h
@@ -21,6 +21,7 @@
 
 class PermissionPromptChipModel;
 class LocationBarView;
+class PermissionDashboardView;
 // ButtonController that NotifyClick from being called when the
 // BubbleOwnerDelegate's bubble is showing. Otherwise the bubble will show again
 // immediately after being closed via losing focus.
@@ -42,7 +43,9 @@
                        public BubbleOwnerDelegate,
                        public OmniboxChipButton::Observer {
  public:
-  ChipController(Browser* browser_, OmniboxChipButton* chip_view);
+  ChipController(Browser* browser,
+                 OmniboxChipButton* chip_view,
+                 PermissionDashboardView* permission_dashboard_view = nullptr);
 
   ~ChipController() override;
   ChipController(const ChipController&) = delete;
@@ -207,10 +210,13 @@
   bool is_confirmation_showing_ = false;
   bool is_waiting_for_confirmation_collapse_ = false;
 
+  raw_ptr<Browser> browser_;
+
   // The chip view this controller modifies.
   raw_ptr<OmniboxChipButton> chip_;
 
-  raw_ptr<Browser> browser_;
+  // `PermissionDashboardView` is owner of OmniboxChipButton.
+  raw_ptr<PermissionDashboardView> permission_dashboard_view_;
 
   // The time when the request chip was displayed.
   base::TimeTicks request_chip_shown_time_;
diff --git a/chrome/browser/ui/views/permissions/permission_bubble_interactive_uitest.cc b/chrome/browser/ui/views/permissions/permission_bubble_interactive_uitest.cc
index bf0b7dd..b26616e 100644
--- a/chrome/browser/ui/views/permissions/permission_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/permissions/permission_bubble_interactive_uitest.cc
@@ -120,9 +120,9 @@
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
     LocationBarView* lbv = browser_view->toolbar()->location_bar();
-    if (lbv->chip_controller()->IsPermissionPromptChipVisible() &&
-        !lbv->chip_controller()->IsBubbleShowing()) {
-      views::test::ButtonTestApi(lbv->chip_controller()->chip())
+    if (lbv->GetChipController()->IsPermissionPromptChipVisible() &&
+        !lbv->GetChipController()->IsBubbleShowing()) {
+      views::test::ButtonTestApi(lbv->GetChipController()->chip())
           .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
                                       gfx::Point(), ui::EventTimeForNow(),
                                       ui::EF_LEFT_MOUSE_BUTTON, 0));
diff --git a/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc b/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
index 5246a536..41d620c 100644
--- a/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
+++ b/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
@@ -158,7 +158,7 @@
   }
 
   OmniboxChipButton* GetChip() {
-    return GetLocationBarView()->chip_controller()->chip();
+    return GetLocationBarView()->GetChipController()->chip();
   }
 
   ChipController* GetChipController() {
@@ -166,7 +166,7 @@
         BrowserView::GetBrowserViewForBrowser(browser());
     LocationBarView* lbv = browser_view->toolbar()->location_bar();
 
-    return lbv->chip_controller();
+    return lbv->GetChipController();
   }
 
   PermissionPromptChip* GetPermissionPromptChip() {
@@ -1592,7 +1592,7 @@
   LocationBarView* location_bar =
       BrowserView::GetBrowserViewForBrowser(browser())->GetLocationBarView();
   ASSERT_TRUE(location_bar);
-  ChipController* chip_controller = location_bar->chip_controller();
+  ChipController* chip_controller = location_bar->GetChipController();
   ChipExpansionObserver chip_expansion_observer(chip_controller->chip());
 
   EXPECT_FALSE(manager->IsRequestInProgress());
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
index bc796ee..ff70d77 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
@@ -21,7 +21,7 @@
   LocationBarView* lbv = GetLocationBarView();
   if (lbv && lbv->IsDrawn() &&
       delegate->Requests()[0]->IsConfirmationChipSupported()) {
-    lbv->chip_controller()->InitializePermissionPrompt(
+    lbv->GetChipController()->InitializePermissionPrompt(
         delegate->GetWeakPtr(),
         base::BindOnce(&PermissionPromptBubble::ShowBubble,
                        weak_factory_.GetWeakPtr()));
@@ -127,7 +127,7 @@
 
     if (lbv && lbv->IsDrawn() && !lbv->GetWidget()->IsFullscreen() &&
         !lbv->IsEditingOrEmpty()) {
-      auto* chip_controller = lbv->chip_controller();
+      auto* chip_controller = lbv->GetChipController();
       chip_controller->InitializePermissionPrompt(delegate()->GetWeakPtr());
     }
   }
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
index 581f004..bd602e9 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
@@ -141,7 +141,7 @@
   ChipController* GetChipController() {
     BrowserView* browser_view =
         BrowserView::GetBrowserViewForBrowser(browser());
-    return browser_view->toolbar()->location_bar()->chip_controller();
+    return browser_view->toolbar()->location_bar()->GetChipController();
   }
 
   ContentSettingImageView& GetContentSettingImageView(
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_chip.cc b/chrome/browser/ui/views/permissions/permission_prompt_chip.cc
index 357452ab..486ef2201 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_chip.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_chip.cc
@@ -33,10 +33,6 @@
   DCHECK(delegate_);
   LocationBarView* lbv = GetLocationBarView();
 
-  if (!lbv->chip_controller()->chip()) {
-    lbv->CreateChip();
-  }
-
   // Before showing a chip make sure the LocationBar is in a valid state. That
   // fixes a bug when a chip overlays the padlock icon.
   lbv->InvalidateLayout();
@@ -44,7 +40,7 @@
   if (delegate->ShouldCurrentRequestUseQuietUI())
     PreemptivelyResolvePermissionRequest(web_contents, delegate);
 
-  chip_controller_ = lbv->chip_controller();
+  chip_controller_ = lbv->GetChipController();
   chip_controller_->ShowPermissionPrompt(delegate->GetWeakPtr());
 }
 
@@ -107,8 +103,8 @@
   LocationBarView* lbv = GetLocationBarView();
 
   return chip_controller_->IsPermissionPromptChipVisible() &&
-                 lbv->chip_controller()->IsBubbleShowing()
-             ? lbv->chip_controller()->GetBubbleWidget()
+                 lbv->GetChipController()->IsBubbleShowing()
+             ? lbv->GetChipController()->GetBubbleWidget()
              : nullptr;
 }
 
diff --git a/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc b/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
index a922bea..878103b 100644
--- a/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
+++ b/chrome/browser/ui/views/permissions/permission_request_chip_browsertest.cc
@@ -61,7 +61,7 @@
                        ChipFinalizedWhenInteractingWithOmnibox) {
   RequestPermission(browser());
   LocationBarView* lbv = GetLocationBarView(browser());
-  auto* animation = lbv->chip_controller()->chip()->animation_for_testing();
+  auto* animation = lbv->GetChipController()->chip()->animation_for_testing();
 
   // Animate the chip expand.
   gfx::AnimationTestApi animation_api(animation);
@@ -71,12 +71,14 @@
 
   // After animation ended, the chip is expanded and the bubble is shown because
   // the gesture sensitive request feature is enabled.
-  EXPECT_TRUE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_TRUE(lbv->chip_controller()->IsBubbleShowing());
+  EXPECT_TRUE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_TRUE(lbv->GetChipController()->IsBubbleShowing());
 
   // Because the bubble is shown, callback timers should be abandoned
-  EXPECT_FALSE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()->is_dismiss_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_dismiss_timer_running_for_testing());
 
   // Type something in the omnibox.
   auto* omnibox_view = lbv->GetOmniboxView();
@@ -87,8 +89,8 @@
 
   // While the user is interacting with the omnibox, the chip is hidden, the
   // location icon isn't offset by the chip and the bubble is hidden.
-  EXPECT_FALSE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_FALSE(lbv->chip_controller()->IsBubbleShowing());
+  EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(lbv->GetChipController()->IsBubbleShowing());
   if (!features::IsChromeRefresh2023() &&
       !OmniboxFieldTrial::IsCr23LayoutEnabled()) {
     // CR2023 has a few experimental flavors of LocationIconView positioning.
@@ -99,8 +101,10 @@
   }
 
   // Ensure no callbacks are pending.
-  EXPECT_FALSE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()->is_dismiss_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_dismiss_timer_running_for_testing());
 }
 
 IN_PROC_BROWSER_TEST_F(PermissionRequestChipGestureSensitiveBrowserTest,
@@ -108,7 +112,7 @@
   LocationBarView* lbv = GetLocationBarView(browser());
 
   // The chip is not shown because there is no active permission request.
-  EXPECT_FALSE(lbv->chip_controller()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
 
   // Type something in the omnibox.
   auto* omnibox_view = lbv->GetOmniboxView();
@@ -119,7 +123,7 @@
 
   // While the user is interacting with the omnibox, an incoming permission
   // request will be automatically ignored. The chip is not shown.
-  EXPECT_FALSE(lbv->chip_controller()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
 }
 
 // This is an end-to-end test that verifies that a permission prompt bubble will
@@ -257,7 +261,7 @@
   LocationBarView* location_bar =
       BrowserView::GetBrowserViewForBrowser(browser())->GetLocationBarView();
   ASSERT_TRUE(location_bar);
-  ChipController* chip_controller = location_bar->chip_controller();
+  ChipController* chip_controller = location_bar->GetChipController();
 
   // Trigger permission request on first tab
   EXPECT_FALSE(manager_tab_0->IsRequestInProgress());
@@ -306,7 +310,7 @@
                        CallbacksResetWhenInteractingWithOmnibox) {
   RequestPermission(browser());
   LocationBarView* lbv = GetLocationBarView(browser());
-  auto* animation = lbv->chip_controller()->chip()->animation_for_testing();
+  auto* animation = lbv->GetChipController()->chip()->animation_for_testing();
 
   // Animate the chip expand.
   gfx::AnimationTestApi animation_api(animation);
@@ -315,13 +319,15 @@
   animation_api.Step(now + animation->GetSlideDuration());
 
   // After animation ended, the chip is expanded and a bubble is shown.
-  EXPECT_TRUE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_TRUE(lbv->chip_controller()->IsBubbleShowing());
+  EXPECT_TRUE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_TRUE(lbv->GetChipController()->IsBubbleShowing());
 
   // Because a bubble is shown, the collapse callback timer should not be
   // running.
-  EXPECT_FALSE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()->is_dismiss_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_dismiss_timer_running_for_testing());
 
   // Type something in the omnibox.
   auto* omnibox_view = lbv->GetOmniboxView();
@@ -331,9 +337,11 @@
   base::RunLoop().RunUntilIdle();
 
   // Ensure chip is no longer visible and callbacks are no longer running.
-  EXPECT_FALSE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_FALSE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()->is_dismiss_timer_running_for_testing());
+  EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_dismiss_timer_running_for_testing());
 }
 
 class PermissionRequestChipBrowserUiTest : public UiBrowserTest {
@@ -345,7 +353,7 @@
 
   bool VerifyUi() override {
     LocationBarView* const location_bar = GetLocationBarView(browser());
-    OmniboxChipButton* const chip = location_bar->chip_controller()->chip();
+    OmniboxChipButton* const chip = location_bar->GetChipController()->chip();
     if (!chip || !chip->GetVisible() || chip->is_fully_collapsed()) {
       return false;
     }
@@ -383,10 +391,10 @@
   LocationBarView* lbv = GetLocationBarView(browser());
 
   // The chip is expanded and a bubble is shown.
-  EXPECT_TRUE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_TRUE(lbv->chip_controller()->IsBubbleShowing());
+  EXPECT_TRUE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_TRUE(lbv->GetChipController()->IsBubbleShowing());
 
-  lbv->chip_controller()
+  lbv->GetChipController()
       ->active_permission_request_manager_for_testing()
       .value()
       ->Deny();
@@ -394,18 +402,20 @@
   base::RunLoop().RunUntilIdle();
 
   // The chip is visible as we show the confirmation.
-  EXPECT_TRUE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_FALSE(lbv->chip_controller()->IsBubbleShowing());
-  EXPECT_TRUE(lbv->chip_controller()->is_confirmation_showing_for_testing());
-  EXPECT_TRUE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()
+  EXPECT_TRUE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(lbv->GetChipController()->IsBubbleShowing());
+  EXPECT_TRUE(lbv->GetChipController()->is_confirmation_showing_for_testing());
+  EXPECT_TRUE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(lbv->GetChipController()
                    ->is_waiting_for_confirmation_collapse_for_testing());
 
-  lbv->chip_controller()->fire_collapse_timer_for_testing();
+  lbv->GetChipController()->fire_collapse_timer_for_testing();
 
-  EXPECT_FALSE(lbv->chip_controller()->IsPermissionPromptChipVisible());
-  EXPECT_FALSE(lbv->chip_controller()->is_confirmation_showing_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()->is_collapse_timer_running_for_testing());
-  EXPECT_FALSE(lbv->chip_controller()
+  EXPECT_FALSE(lbv->GetChipController()->IsPermissionPromptChipVisible());
+  EXPECT_FALSE(lbv->GetChipController()->is_confirmation_showing_for_testing());
+  EXPECT_FALSE(
+      lbv->GetChipController()->is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(lbv->GetChipController()
                    ->is_waiting_for_confirmation_collapse_for_testing());
 }
diff --git a/chrome/browser/ui/web_applications/BUILD.gn b/chrome/browser/ui/web_applications/BUILD.gn
index 79abee4b..31d46ba 100644
--- a/chrome/browser/ui/web_applications/BUILD.gn
+++ b/chrome/browser/ui/web_applications/BUILD.gn
@@ -51,6 +51,7 @@
     "web_app_menu_model_browsertest.cc",
     "web_app_metrics_browsertest.cc",
     "web_app_navigate_browsertest.cc",
+    "web_app_title_browsertest.cc",
     "web_app_ui_manager_impl_browsertest.cc",
     "web_app_uninstall_browsertest.cc",
   ]
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index b18c4a7..0feda079 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -11,6 +11,7 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -37,6 +38,7 @@
 #include "chrome/browser/web_applications/web_app_sync_bridge.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/services/app_service/public/cpp/app_registry_cache.h"
 #include "components/services/app_service/public/cpp/app_types.h"
@@ -46,6 +48,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
 #include "third_party/blink/public/common/features.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/image/image.h"
 #include "ui/native_theme/native_theme.h"
@@ -575,6 +578,19 @@
 
   std::u16string app_name = base::UTF8ToUTF16(
       provider_->registrar_unsafe().GetAppShortName(app_id()));
+
+  // If app title is set, then use that with the app name as the title.
+  std::u16string app_title;
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  if (web_contents) {
+    app_title = web_contents->GetAppTitle();
+  }
+
+  if (!app_title.empty()) {
+    return l10n_util::GetStringFUTF16(IDS_WEB_APP_WITH_APP_TITLE, app_name,
+                                      app_title);
+  }
   if (base::StartsWith(raw_title, app_name)) {
     return raw_title;
   }
diff --git a/chrome/browser/ui/web_applications/web_app_title_browsertest.cc b/chrome/browser/ui/web_applications/web_app_title_browsertest.cc
new file mode 100644
index 0000000..2eda884
--- /dev/null
+++ b/chrome/browser/ui/web_applications/web_app_title_browsertest.cc
@@ -0,0 +1,159 @@
+// 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/ui/browser.h"
+#include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
+#include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test.h"
+
+namespace web_app {
+
+// Test app title scenarios with valid, empty and dynamic app title.
+class WebAppTitleBrowserTest : public WebAppControllerBrowserTest {
+ public:
+  WebAppTitleBrowserTest() = default;
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "AppTitle");
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(WebAppTitleBrowserTest, ValidAppTitle) {
+  const GURL app_url =
+      https_server()->GetURL("/web_apps/page_with_app_title.html");
+  const std::u16string app_title = u"A Web App";
+
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
+  web_app_info->start_url = app_url;
+  web_app_info->scope = app_url.GetWithoutFilename();
+  web_app_info->title = app_title;
+  const webapps::AppId app_id = InstallWebApp(std::move(web_app_info));
+
+  Browser* const app_browser = LaunchWebAppBrowser(app_id);
+  content::WebContents* const web_contents =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+  // Validate app title has app title.
+  EXPECT_EQ(u"A Web App - AppTitle",
+            app_browser->GetWindowTitleForCurrentTab(false));
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppTitleBrowserTest, WithoutAppTitle) {
+  const GURL app_url =
+      https_server()->GetURL("/web_apps/page_without_app_title.html");
+  const std::u16string app_title = u"A Web App";
+
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
+  web_app_info->start_url = app_url;
+  web_app_info->scope = app_url.GetWithoutFilename();
+  web_app_info->title = app_title;
+  const webapps::AppId app_id = InstallWebApp(std::move(web_app_info));
+
+  Browser* const app_browser = LaunchWebAppBrowser(app_id);
+  content::WebContents* const web_contents =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+  // Validate app title is the same as page title.
+  EXPECT_EQ(u"A Web App", app_browser->GetWindowTitleForCurrentTab(false));
+}
+
+IN_PROC_BROWSER_TEST_F(WebAppTitleBrowserTest, DynamicAppTitle) {
+  const GURL app_url =
+      https_server()->GetURL("/web_apps/page_without_app_title.html");
+  const std::u16string app_title = u"A Web App";
+
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
+  web_app_info->start_url = app_url;
+  web_app_info->scope = app_url.GetWithoutFilename();
+  web_app_info->title = app_title;
+  const webapps::AppId app_id = InstallWebApp(std::move(web_app_info));
+
+  Browser* const app_browser = LaunchWebAppBrowser(app_id);
+  content::WebContents* const web_contents =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+  // Validate that app title matches page title.
+  EXPECT_EQ(u"A Web App", app_browser->GetWindowTitleForCurrentTab(false));
+
+  {
+    // Add app title via script and validate title is updated.
+    std::string add_app_title =
+        "var meta = document.createElement('meta'); meta.name = 'app-title'; "
+        "meta.content = 'AppTitle'; "
+        "document.getElementsByTagName('head')[0].appendChild(meta);";
+    EXPECT_TRUE(content::ExecJs(web_contents, add_app_title));
+    EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+    EXPECT_EQ(u"A Web App - AppTitle",
+              app_browser->GetWindowTitleForCurrentTab(false));
+  }
+
+  {
+    // Update app title via script and validate title is updated.
+    std::string update_app_title =
+        "document.head.getElementsByTagName('meta')['app-title'].content = "
+        "'New'";
+    EXPECT_TRUE(content::ExecJs(web_contents, update_app_title));
+    EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+    EXPECT_EQ(u"A Web App - New",
+              app_browser->GetWindowTitleForCurrentTab(false));
+  }
+
+  {
+    // Remove app title via script and validate title is updated.
+    std::string remove_app_title =
+        "document.head.getElementsByTagName('meta')['app-title'].remove()";
+    EXPECT_TRUE(content::ExecJs(web_contents, remove_app_title));
+    EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+    EXPECT_EQ(u"A Web App", app_browser->GetWindowTitleForCurrentTab(false));
+  }
+}
+
+// Navigate to page with and without app title to validate app title is updated.
+IN_PROC_BROWSER_TEST_F(WebAppTitleBrowserTest, AppTitleNavigation) {
+  const GURL app_url =
+      https_server()->GetURL("/web_apps/page_with_app_title.html");
+  const std::u16string app_title = u"A Web App";
+
+  auto web_app_info = std::make_unique<WebAppInstallInfo>(
+      GenerateManifestIdFromStartUrlOnly(app_url));
+  web_app_info->start_url = app_url;
+  web_app_info->scope = app_url.GetWithoutFilename();
+  web_app_info->title = app_title;
+  const webapps::AppId app_id = InstallWebApp(std::move(web_app_info));
+
+  Browser* const app_browser = LaunchWebAppBrowser(app_id);
+  content::WebContents* const web_contents =
+      app_browser->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+  // Validate app title has app title.
+  EXPECT_EQ(u"A Web App - AppTitle",
+            app_browser->GetWindowTitleForCurrentTab(false));
+
+  // Navigate to page without app title.
+  const GURL page_without_url =
+      https_server()->GetURL("/web_apps/page_without_app_title.html");
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(app_browser, page_without_url));
+  EXPECT_EQ(u"A Web App", app_browser->GetWindowTitleForCurrentTab(false));
+
+  // Navigate to page with app title.
+  web_contents->GetController().GoBack();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+  EXPECT_EQ(u"A Web App - AppTitle",
+            app_browser->GetWindowTitleForCurrentTab(false));
+
+  // Navigate again to page without app title.
+  web_contents->GetController().GoForward();
+  EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+  EXPECT_EQ(u"A Web App", app_browser->GetWindowTitleForCurrentTab(false));
+}
+}  // namespace web_app
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
index 0869d2b..c6fa7c6 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.cc
@@ -60,7 +60,6 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
 #include "ui/gfx/geometry/size.h"
-#include "ui/gfx/native_widget_types.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
 
@@ -397,11 +396,9 @@
     Profile* profile,
     const std::vector<storage::FileSystemURL>& file_urls,
     const CloudProvider cloud_provider,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<CloudOpenMetrics> cloud_open_metrics) {
-  scoped_refptr<CloudOpenTask> upload_task = WrapRefCounted(
-      new CloudOpenTask(profile, file_urls, cloud_provider, modal_parent,
-                        std::move(cloud_open_metrics)));
+  scoped_refptr<CloudOpenTask> upload_task = WrapRefCounted(new CloudOpenTask(
+      profile, file_urls, cloud_provider, std::move(cloud_open_metrics)));
   // Keep `upload_task` alive until `TaskFinished` executes.
   bool status = upload_task->ExecuteInternal();
   return status;
@@ -411,12 +408,10 @@
     Profile* profile,
     std::vector<storage::FileSystemURL> file_urls,
     const CloudProvider cloud_provider,
-    gfx::NativeWindow modal_parent,
     std::unique_ptr<CloudOpenMetrics> cloud_open_metrics)
     : profile_(profile),
       file_urls_(file_urls),
       cloud_provider_(cloud_provider),
-      modal_parent_(modal_parent),
       cloud_open_metrics_(std::move(cloud_open_metrics)) {}
 
 CloudOpenTask::~CloudOpenTask() = default;
@@ -991,8 +986,9 @@
 // Creates and shows a new dialog for the cloud upload workflow. If there are
 // local file tasks from `resulting_tasks`, include them in the dialog
 // arguments. These tasks are can be selected by the user to open the files
-// instead of using a cloud provider. If no modal_parent was provided, first
-// launches a new Files app window, which we listen for in OnBrowserAdded().
+// instead of using a cloud provider. If there is no Files app window currently
+// open to use as a modal parent for the dialog, first launches a new Files app
+// window, which we listen for in OnBrowserAdded().
 void CloudOpenTask::ShowDialog(
     mojom::DialogArgsPtr args,
     std::unique_ptr<fm_tasks::ResultingTasks> resulting_tasks) {
@@ -1031,7 +1027,13 @@
       std::move(args), base::BindOnce(&CloudOpenTask::OnDialogComplete, this),
       office_move_confirmation_shown);
 
-  if (!modal_parent_) {
+  // Get Files App window, if it exists.
+  Browser* browser =
+      FindSystemWebAppBrowser(profile_, ash::SystemWebAppType::FILE_MANAGER);
+  gfx::NativeWindow modal_parent =
+      browser ? browser->window()->GetNativeWindow() : nullptr;
+
+  if (!modal_parent) {
     BrowserList::AddObserver(this);
     DCHECK(!pending_dialog_);
     pending_dialog_ = dialog;
@@ -1041,7 +1043,7 @@
     file_manager::util::ShowItemInFolder(profile_, file_urls_.at(0).path(),
                                          base::DoNothing());
   } else {
-    dialog->ShowSystemDialog(modal_parent_);
+    dialog->ShowSystemDialog(modal_parent);
   }
 }
 
@@ -1087,8 +1089,7 @@
   }
   BrowserList::RemoveObserver(this);
 
-  modal_parent_ = browser->window()->GetNativeWindow();
-  pending_dialog_->ShowSystemDialog(modal_parent_);
+  pending_dialog_->ShowSystemDialog(browser->window()->GetNativeWindow());
   // The dialog is deleted in `SystemWebDialogDelegate::OnDialogClosed`.
   pending_dialog_ = nullptr;
 }
@@ -1210,7 +1211,7 @@
                                 ? OfficeSetupFileHandler::kQuickOffice
                                 : OfficeSetupFileHandler::kOtherLocalHandler);
   fm_tasks::ExecuteFileTask(
-      profile_, task, file_urls_, nullptr,
+      profile_, task, file_urls_,
       base::BindOnce(&CloudOpenTask::LocalTaskExecuted, this, task));
 }
 
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
index 86613f1..69a9094 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog.h
@@ -26,6 +26,7 @@
 #include "components/drive/file_errors.h"
 #include "storage/browser/file_system/file_system_url.h"
 #include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
 
 class Profile;
 
@@ -107,7 +108,6 @@
   static bool Execute(Profile* profile,
                       const std::vector<storage::FileSystemURL>& file_urls,
                       const CloudProvider cloud_provider,
-                      gfx::NativeWindow modal_parent,
                       std::unique_ptr<CloudOpenMetrics> cloud_open_metrics);
 
   // Set the local tasks that are passed to the File Handler dialog. Normally
@@ -159,7 +159,6 @@
   CloudOpenTask(Profile* profile,
                 std::vector<storage::FileSystemURL> file_urls,
                 const CloudProvider cloud_provider,
-                gfx::NativeWindow modal_parent,
                 std::unique_ptr<CloudOpenMetrics> cloud_open_metrics);
 
   ~CloudOpenTask() override;
@@ -234,7 +233,6 @@
   raw_ptr<Profile, DanglingUntriaged> profile_;
   std::vector<storage::FileSystemURL> file_urls_;
   CloudProvider cloud_provider_;
-  gfx::NativeWindow modal_parent_;
   std::unique_ptr<CloudOpenMetrics> cloud_open_metrics_;
   std::vector<::file_manager::file_tasks::TaskDescriptor> local_tasks_;
   size_t pending_uploads_ = 0;
diff --git a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog_browsertest.cc b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog_browsertest.cc
index 4b7f559..85c77bf 100644
--- a/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/ash/cloud_upload/cloud_upload_dialog_browsertest.cc
@@ -255,8 +255,7 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Tests that a new Files app window is created if no modal parent window is
-// passed in.
+// Tests that a new Files app window is created if no Files app window exists.
 IN_PROC_BROWSER_TEST_F(FileHandlerDialogBrowserTest, NewModalParentCreated) {
   file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
 
@@ -271,7 +270,7 @@
 
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(
-      profile(), files_, CloudProvider::kGoogleDrive, nullptr,
+      profile(), files_, CloudProvider::kGoogleDrive,
       std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                          /*file_count=*/1)));
 
@@ -283,51 +282,10 @@
   ASSERT_NE(nullptr, browser);
 }
 
-// Tests that a new Files app window is created even if there is a Files app
-// window open, but it's not the window that was passed in to CloudOpenTask.
-IN_PROC_BROWSER_TEST_F(FileHandlerDialogBrowserTest,
-                       NewModalParentCreatedWithExisting) {
-  file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
-
-  Browser* browser =
-      FindSystemWebAppBrowser(profile(), SystemWebAppType::FILE_MANAGER);
-  ASSERT_EQ(nullptr, browser);
-
-  // Open a files app window.
-  base::test::TestFuture<platform_util::OpenOperationResult> future;
-  file_manager::util::ShowItemInFolder(profile(), files_.at(0).path(),
-                                       future.GetCallback());
-  EXPECT_EQ(future.Get(), platform_util::OpenOperationResult::OPEN_SUCCEEDED);
-  Browser* first_files_app = ui_test_utils::WaitForBrowserToOpen();
-
-  browser = FindSystemWebAppBrowser(profile(), SystemWebAppType::FILE_MANAGER);
-  ASSERT_NE(nullptr, browser);
-  ASSERT_EQ(first_files_app, browser);
-
-  // Watch for File Handler dialog URL chrome://cloud-upload.
-  content::TestNavigationObserver navigation_observer_dialog(
-      (GURL(chrome::kChromeUICloudUploadURL)));
-  navigation_observer_dialog.StartWatchingNewWebContents();
-
-  // Launch File Handler dialog.
-  ASSERT_TRUE(CloudOpenTask::Execute(
-      profile(), files_, CloudProvider::kGoogleDrive, nullptr,
-      std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
-                                         /*file_count=*/1)));
-  // Check that a new browser opened.
-  Browser* new_browser = ui_test_utils::WaitForBrowserToOpen();
-  ASSERT_NE(new_browser, first_files_app);
-  ASSERT_TRUE(
-      IsBrowserForSystemWebApp(new_browser, SystemWebAppType::FILE_MANAGER));
-
-  // Wait for File Handler dialog to open at chrome://cloud-upload.
-  navigation_observer_dialog.Wait();
-  ASSERT_TRUE(navigation_observer_dialog.last_navigation_succeeded());
-}
-
 // Tests that a new Files app window is not created when there is a Files app
-// window already open, and it's passed in as the modal parent.
-IN_PROC_BROWSER_TEST_F(FileHandlerDialogBrowserTest, ModalParentProvided) {
+// window already open.
+IN_PROC_BROWSER_TEST_F(FileHandlerDialogBrowserTest,
+                       ExistingWindowUsedAsModalParent) {
   file_manager::test::AddDefaultComponentExtensionsOnMainThread(profile());
 
   Browser* browser =
@@ -354,7 +312,6 @@
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(
       profile(), files_, CloudProvider::kGoogleDrive,
-      browser->window()->GetNativeWindow(),
       std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                          /*file_count=*/1)));
 
@@ -387,7 +344,7 @@
 
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(profile(), files_,
-                                     CloudProvider::kGoogleDrive, nullptr,
+                                     CloudProvider::kGoogleDrive,
                                      std::move(cloud_open_metrics)));
 
   // Wait for File Handler dialog to open at chrome://cloud-upload.
@@ -438,7 +395,7 @@
 
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(profile(), files_,
-                                     CloudProvider::kGoogleDrive, nullptr,
+                                     CloudProvider::kGoogleDrive,
                                      std::move(cloud_open_metrics)));
 
   // Wait for File Handler dialog to open at chrome://cloud-upload.
@@ -568,7 +525,7 @@
 
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(
-      profile(), files_, CloudProvider::kGoogleDrive, nullptr,
+      profile(), files_, CloudProvider::kGoogleDrive,
       std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                          /*file_count=*/1)));
 
@@ -725,7 +682,6 @@
                                            : CloudProvider::kOneDrive;
   ASSERT_TRUE(CloudOpenTask::Execute(
       profile(), {doc_file}, cloud_provider,
-      /*modal_parent=*/nullptr,
       std::make_unique<CloudOpenMetrics>(cloud_provider, /*file_count=*/1)));
 
   // Wait for File Handler dialog to open at chrome://cloud-upload.
@@ -784,7 +740,7 @@
 
   // Launch File Handler dialog.
   ASSERT_TRUE(CloudOpenTask::Execute(
-      profile(), {doc_file}, CloudProvider::kOneDrive, /*modal_parent=*/nullptr,
+      profile(), {doc_file}, CloudProvider::kOneDrive,
       std::make_unique<CloudOpenMetrics>(CloudProvider::kOneDrive,
                                          /*file_count=*/1)));
 
@@ -955,7 +911,7 @@
           ? CloudProvider::kGoogleDrive
           : CloudProvider::kOneDrive;
   ASSERT_TRUE(CloudOpenTask::Execute(
-      profile(), files_, cloud_provider, nullptr,
+      profile(), files_, cloud_provider,
       std::make_unique<CloudOpenMetrics>(cloud_provider, /*file_count=*/1)));
 
   // Wait for File Handler dialog to open at chrome://cloud-upload.
@@ -1154,7 +1110,7 @@
 
     auto cloud_open_task = base::WrapRefCounted(
         new CloudOpenTask(profile(), files_, CloudProvider::kGoogleDrive,
-                          nullptr, std::move(cloud_open_metrics)));
+                          std::move(cloud_open_metrics)));
     cloud_open_task->SetTasksForTest(tasks_);
 
     for (int selected_task = 0; selected_task < num_tasks_; selected_task++) {
@@ -1189,7 +1145,7 @@
   {
     auto cloud_open_task = base::WrapRefCounted(
         new CloudOpenTask(profile(), files_, CloudProvider::kGoogleDrive,
-                          nullptr, std::move(cloud_open_metrics)));
+                          std::move(cloud_open_metrics)));
     cloud_open_task->SetTasksForTest(tasks_);
 
     int out_of_range_task = num_tasks_;
@@ -1278,10 +1234,9 @@
       (GURL(chrome::kChromeUICloudUploadURL)));
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait(browser()->profile());
+  LaunchFilesAppAndWait(browser()->profile());
 
   CloudOpenTask::Execute(profile(), files_, CloudProvider::kOneDrive,
-                         modal_parent,
                          std::make_unique<CloudOpenMetrics>(
                              CloudProvider::kOneDrive, /*file_count=*/1));
 
@@ -1325,10 +1280,9 @@
       (GURL(chrome::kChromeUICloudUploadURL)));
   navigation_observer_dialog.StartWatchingNewWebContents();
 
-  gfx::NativeWindow modal_parent = LaunchFilesAppAndWait(browser()->profile());
+  LaunchFilesAppAndWait(browser()->profile());
 
   CloudOpenTask::Execute(profile(), files_, CloudProvider::kOneDrive,
-                         modal_parent,
                          std::make_unique<CloudOpenMetrics>(
                              CloudProvider::kOneDrive, /*file_count=*/1));
 
@@ -1386,7 +1340,7 @@
   AddFakeOfficePWA();
 
   auto cloud_open_task = base::WrapRefCounted(
-      new CloudOpenTask(profile(), files_, CloudProvider::kOneDrive, nullptr,
+      new CloudOpenTask(profile(), files_, CloudProvider::kOneDrive,
                         std::make_unique<CloudOpenMetrics>(
                             CloudProvider::kOneDrive, /*file_count=*/1)));
   mojom::DialogArgsPtr args =
@@ -1461,7 +1415,7 @@
   AddFakeOfficePWA();
 
   auto cloud_open_task = base::WrapRefCounted(
-      new CloudOpenTask(profile(), files_, CloudProvider::kOneDrive, nullptr,
+      new CloudOpenTask(profile(), files_, CloudProvider::kOneDrive,
                         std::make_unique<CloudOpenMetrics>(
                             CloudProvider::kOneDrive, /*file_count=*/1)));
   mojom::DialogArgsPtr args =
@@ -1531,7 +1485,7 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_,
-        ash::cloud_upload::CloudProvider::kGoogleDrive, nullptr,
+        ash::cloud_upload::CloudProvider::kGoogleDrive,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                            /*file_count=*/1)));
   }
@@ -1546,7 +1500,7 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_,
-        ash::cloud_upload::CloudProvider::kGoogleDrive, nullptr,
+        ash::cloud_upload::CloudProvider::kGoogleDrive,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                            /*file_count=*/1)));
   }
@@ -1561,7 +1515,7 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_,
-        ash::cloud_upload::CloudProvider::kGoogleDrive, nullptr,
+        ash::cloud_upload::CloudProvider::kGoogleDrive,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kGoogleDrive,
                                            /*file_count=*/1)));
   }
@@ -1576,7 +1530,6 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_, ash::cloud_upload::CloudProvider::kOneDrive,
-        nullptr,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kOneDrive,
                                            /*file_count=*/1)));
   }
@@ -1591,7 +1544,6 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_, ash::cloud_upload::CloudProvider::kOneDrive,
-        nullptr,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kOneDrive,
                                            /*file_count=*/1)));
   }
@@ -1606,7 +1558,6 @@
 
     upload_task_ = base::WrapRefCounted(new ash::cloud_upload::CloudOpenTask(
         profile(), source_files_, ash::cloud_upload::CloudProvider::kOneDrive,
-        nullptr,
         std::make_unique<CloudOpenMetrics>(CloudProvider::kOneDrive,
                                            /*file_count=*/1)));
   }
diff --git a/chrome/browser/ui/webui/compose/compose_ui.cc b/chrome/browser/ui/webui/compose/compose_ui.cc
index b734ebd..8d6fe35 100644
--- a/chrome/browser/ui/webui/compose/compose_ui.cc
+++ b/chrome/browser/ui/webui/compose/compose_ui.cc
@@ -54,6 +54,7 @@
       {"inputFooter", IDS_COMPOSE_FOOTER_FISHFOOD},
       {"submitButton", IDS_COMPOSE_SUBMIT_BUTTON},
       {"resultFooter", IDS_COMPOSE_FOOTER_FISHFOOD},
+      {"onDeviceUsedFooter", IDS_COMPOSE_FEEDBACK_PLACEHOLDER},
       {"insertButton", IDS_COMPOSE_INSERT_BUTTON},
       {"replaceButton", IDS_COMPOSE_REPLACE_BUTTON},
       {"lengthMenuTitle", IDS_COMPOSE_MENU_LENGTH_TITLE},
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals.mojom b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals.mojom
index b62060e..adfcbc88 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals.mojom
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals.mojom
@@ -33,9 +33,14 @@
   GetTpcdSupport() => (
     array<content_settings.mojom.ContentSettingPatternSource> content_settings);
 
-  // Convert PatternParts into a string representation.
+  // Convert ContentSettingsPattern into a string representation.
   ContentSettingsPatternToString(
     content_settings.mojom.ContentSettingsPattern pattern) => (string s);
+
+  // Converts a string into a ContentSettingsPattern.
+  StringToContentSettingsPattern(string s) => (
+    content_settings.mojom.ContentSettingsPattern pattern);
+
 };
 
 
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.cc b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.cc
index 920e994..c7cc2940 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.cc
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.cc
@@ -71,4 +71,10 @@
   std::move(callback).Run(pattern.ToString());
 }
 
+void PrivacySandboxInternalsHandler::StringToContentSettingsPattern(
+    const std::string& s,
+    StringToContentSettingsPatternCallback callback) {
+  std::move(callback).Run(ContentSettingsPattern::FromString(s));
+}
+
 }  // namespace privacy_sandbox_internals
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.h b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.h
index 892fd5d..ae2329fb 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.h
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler.h
@@ -34,6 +34,11 @@
   void ContentSettingsPatternToString(
       const ContentSettingsPattern& pattern,
       ContentSettingsPatternToStringCallback callback) override;
+
+  void StringToContentSettingsPattern(
+      const std::string& s,
+      StringToContentSettingsPatternCallback callback) override;
+
   void GetCookieSettings(GetCookieSettingsCallback callback) override;
   void GetTpcdMetadataGrants(GetTpcdMetadataGrantsCallback callback) override;
   void GetTpcdHeuristicsGrants(GetTpcdMetadataGrantsCallback callback) override;
diff --git a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler_browsertest.cc b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler_browsertest.cc
index 84cc62e5..b9d77d1 100644
--- a/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/privacy_sandbox/privacy_sandbox_internals_handler_browsertest.cc
@@ -93,6 +93,11 @@
     waiter_.Notify();
   }
 
+  void ContentSettingsPatternCallback(const ContentSettingsPattern& pattern) {
+    content_settings_pattern_cb_data_ = pattern;
+    waiter_.Notify();
+  }
+
  protected:
   mojo::Remote<PageHandler> remote_;
   std::unique_ptr<PrivacySandboxInternalsHandler> handler_;
@@ -103,6 +108,7 @@
   std::vector<ContentSettingPatternSource> content_settings_cb_data_;
   std::string string_cb_data_;
   base::Value value_cb_data_;
+  ContentSettingsPattern content_settings_pattern_cb_data_;
 };
 
 IN_PROC_BROWSER_TEST_F(PrivacySandboxInternalsMojoTest, ReadPref) {
@@ -211,7 +217,8 @@
                 Field(&ContentSettingPatternSource::source, "preference")))));
 }
 
-IN_PROC_BROWSER_TEST_F(PrivacySandboxInternalsMojoTest, PatternPartsToString) {
+IN_PROC_BROWSER_TEST_F(PrivacySandboxInternalsMojoTest,
+                       ContentSettingsPatternToString) {
   for (const std::string& regex :
        {"[*.]example.com", "http://example.net", "example.org"}) {
     ContentSettingsPattern pattern = ContentSettingsPattern::FromString(regex);
@@ -225,5 +232,21 @@
   }
 }
 
+IN_PROC_BROWSER_TEST_F(PrivacySandboxInternalsMojoTest,
+                       StringToContentSettingsPattern) {
+  for (const std::string& regex :
+       {"[*.]example.com", "http://example.net", "example.org"}) {
+    remote_->StringToContentSettingsPattern(
+        regex,
+        base::BindOnce(
+            &PrivacySandboxInternalsMojoTest::ContentSettingsPatternCallback,
+            base::Unretained(this)));
+    ContentSettingsPattern expected_pattern =
+        ContentSettingsPattern::FromString(regex);
+    waiter_.Wait();
+    waiter_.Reset();
+    EXPECT_THAT(content_settings_pattern_cb_data_, Eq(expected_pattern));
+  }
+}
 }  // namespace
 }  // namespace privacy_sandbox_internals
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index dc92e65..e32ef16 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -184,62 +184,64 @@
 
 void AddCommonStrings(content::WebUIDataSource* html_source, Profile* profile) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"add", IDS_ADD},
-    {"advancedPageTitle", IDS_SETTINGS_ADVANCED},
-    {"back", IDS_ACCNAME_BACK},
-    {"basicPageTitle", IDS_SETTINGS_BASIC},
-    {"cancel", IDS_CANCEL},
-    {"clear", IDS_SETTINGS_CLEAR},
-    {"close", IDS_CLOSE},
-    {"confirm", IDS_CONFIRM},
-    {"continue", IDS_SETTINGS_CONTINUE},
-    {"controlledByExtension", IDS_SETTINGS_CONTROLLED_BY_EXTENSION},
-    {"custom", IDS_SETTINGS_CUSTOM},
-    {"delete", IDS_SETTINGS_DELETE},
-    {"disable", IDS_DISABLE},
-    {"done", IDS_DONE},
-    {"edit", IDS_SETTINGS_EDIT},
-    {"extensionsLinkTooltip", IDS_SETTINGS_MENU_EXTENSIONS_LINK_TOOLTIP},
-    {"fonts", IDS_SETTINGS_FONTS},
-    {"learnMore", IDS_LEARN_MORE},
-    {"manage", IDS_SETTINGS_MANAGE},
-    {"menu", IDS_MENU},
-    {"menuButtonLabel", IDS_SETTINGS_MENU_BUTTON_LABEL},
-    {"moreActions", IDS_SETTINGS_MORE_ACTIONS},
-    {"noThanks", IDS_NO_THANKS},
-    {"ok", IDS_OK},
-    {"opensInNewTab", IDS_SETTINGS_OPENS_IN_NEW_TAB},
-    {"sendFeedbackButton", IDS_SETTINGS_SEND_FEEDBACK_ROLE_DESCRIPTION},
+      {"add", IDS_ADD},
+      {"advancedPageTitle", IDS_SETTINGS_ADVANCED},
+      {"back", IDS_ACCNAME_BACK},
+      {"basicPageTitle", IDS_SETTINGS_BASIC},
+      {"cancel", IDS_CANCEL},
+      {"clear", IDS_SETTINGS_CLEAR},
+      {"close", IDS_CLOSE},
+      {"confirm", IDS_CONFIRM},
+      {"continue", IDS_SETTINGS_CONTINUE},
+      {"controlledByExtension", IDS_SETTINGS_CONTROLLED_BY_EXTENSION},
+      {"custom", IDS_SETTINGS_CUSTOM},
+      {"delete", IDS_SETTINGS_DELETE},
+      {"disable", IDS_DISABLE},
+      {"done", IDS_DONE},
+      {"edit", IDS_SETTINGS_EDIT},
+      {"extensionsLinkTooltip", IDS_SETTINGS_MENU_EXTENSIONS_LINK_TOOLTIP},
+      {"fonts", IDS_SETTINGS_FONTS},
+      {"learnMore", IDS_LEARN_MORE},
+      {"manage", IDS_SETTINGS_MANAGE},
+      {"menu", IDS_MENU},
+      {"menuButtonLabel", IDS_SETTINGS_MENU_BUTTON_LABEL},
+      {"moreActions", IDS_SETTINGS_MORE_ACTIONS},
+      {"noThanks", IDS_NO_THANKS},
+      {"ok", IDS_OK},
+      {"opensInNewTab", IDS_SETTINGS_OPENS_IN_NEW_TAB},
+      {"sendFeedbackButton", IDS_SETTINGS_SEND_FEEDBACK_ROLE_DESCRIPTION},
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-    {"relaunchConfirmationDialogTitle", IDS_RELAUNCH_CONFIRMATION_DIALOG_TITLE},
+      {"relaunchConfirmationDialogTitle",
+       IDS_RELAUNCH_CONFIRMATION_DIALOG_TITLE},
 #endif
-    {"remove", IDS_REMOVE},
-    {"restart", IDS_SETTINGS_RESTART},
-    {"restartToApplyChanges", IDS_SETTINGS_RESTART_TO_APPLY_CHANGES},
-    {"retry", IDS_SETTINGS_RETRY},
-    {"save", IDS_SAVE},
-    {"searchResultBubbleText", IDS_SEARCH_RESULT_BUBBLE_TEXT},
-    {"searchResultsBubbleText", IDS_SEARCH_RESULTS_BUBBLE_TEXT},
-    {"sentenceEnd", IDS_SENTENCE_END},
-    {"settings", IDS_SETTINGS_SETTINGS},
-    {"settingsAltPageTitle", IDS_SETTINGS_ALT_PAGE_TITLE},
-    {"subpageArrowRoleDescription", IDS_SETTINGS_SUBPAGE_BUTTON},
-    {"subpageBackButtonAriaLabel", IDS_SETTINGS_SUBPAGE_BACK_BUTTON_ARIA_LABEL},
-    {"subpageBackButtonAriaRoleDescription",
-     IDS_SETTINGS_SUBPAGE_BACK_BUTTON_ARIA_ROLE_DESCRIPTION},
-    {"subpageLearnMoreAriaLabel", IDS_SETTINGS_SUBPAGE_LEARN_MORE_ARIA_LABEL},
-    {"notValid", IDS_SETTINGS_NOT_VALID},
-    {"notValidWebAddress", IDS_SETTINGS_NOT_VALID_WEB_ADDRESS},
-    {"notValidWebAddressForContentType",
-     IDS_SETTINGS_NOT_VALID_WEB_ADDRESS_FOR_CONTENT_TYPE},
+      {"remove", IDS_REMOVE},
+      {"restart", IDS_SETTINGS_RESTART},
+      {"restartToApplyChanges", IDS_SETTINGS_RESTART_TO_APPLY_CHANGES},
+      {"retry", IDS_SETTINGS_RETRY},
+      {"save", IDS_SAVE},
+      {"searchResultBubbleText", IDS_SEARCH_RESULT_BUBBLE_TEXT},
+      {"searchResultsBubbleText", IDS_SEARCH_RESULTS_BUBBLE_TEXT},
+      {"sentenceEnd", IDS_SENTENCE_END},
+      {"settings", IDS_SETTINGS_SETTINGS},
+      {"settingsAltPageTitle", IDS_SETTINGS_ALT_PAGE_TITLE},
+      {"subpageArrowRoleDescription", IDS_SETTINGS_SUBPAGE_BUTTON},
+      {"subpageBackButtonAriaLabel",
+       IDS_SETTINGS_SUBPAGE_BACK_BUTTON_ARIA_LABEL},
+      {"subpageBackButtonAriaRoleDescription",
+       IDS_SETTINGS_SUBPAGE_BACK_BUTTON_ARIA_ROLE_DESCRIPTION},
+      {"subpageLearnMoreAriaLabel", IDS_SETTINGS_SUBPAGE_LEARN_MORE_ARIA_LABEL},
+      {"notValid", IDS_SETTINGS_NOT_VALID},
+      {"notValidWebAddress", IDS_SETTINGS_NOT_VALID_WEB_ADDRESS},
+      {"notValidWebAddressForContentType",
+       IDS_SETTINGS_NOT_VALID_WEB_ADDRESS_FOR_CONTENT_TYPE},
 
-    // Common font related strings shown in a11y and appearance sections.
-    {"quickBrownFox", IDS_SETTINGS_QUICK_BROWN_FOX},
-    {"verySmall", IDS_SETTINGS_VERY_SMALL_FONT},
-    {"small", IDS_SETTINGS_SMALL_FONT},
-    {"medium", IDS_SETTINGS_MEDIUM_FONT},
-    {"large", IDS_SETTINGS_LARGE_FONT},
-    {"veryLarge", IDS_SETTINGS_VERY_LARGE_FONT},
+      // Common font related strings shown in a11y and appearance sections.
+      {"quickBrownFox", IDS_SETTINGS_QUICK_BROWN_FOX},
+      {"verySmall", IDS_SETTINGS_VERY_SMALL_FONT},
+      {"small", IDS_SETTINGS_SMALL_FONT},
+      {"medium", IDS_SETTINGS_MEDIUM_FONT},
+      {"large", IDS_SETTINGS_LARGE_FONT},
+      {"veryLarge", IDS_SETTINGS_VERY_LARGE_FONT},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -253,7 +255,7 @@
               crosapi::mojom::SessionType::kPublicSession ||
           profile->IsGuestSession());
 #else
-                           profile->IsGuestSession());
+                          profile->IsGuestSession());
 #endif
 
   html_source->AddBoolean("isChildAccount", profile->IsChild());
@@ -273,36 +275,37 @@
 
 void AddA11yStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"moreFeaturesLink", IDS_SETTINGS_MORE_FEATURES_LINK},
-    {"a11yPageTitle", IDS_SETTINGS_ACCESSIBILITY},
-    {"a11yWebStore", IDS_SETTINGS_ACCESSIBILITY_WEB_STORE},
-    {"moreFeaturesLinkDescription",
-     IDS_SETTINGS_MORE_FEATURES_LINK_DESCRIPTION},
-    {"accessibleImageLabelsTitle", IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_TITLE},
-    {"accessibleImageLabelsSubtitle",
-     IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_SUBTITLE},
-    {"settingsSliderRoleDescription",
-     IDS_SETTINGS_SLIDER_MIN_MAX_ARIA_ROLE_DESCRIPTION},
-    {"caretBrowsingTitle", IDS_SETTINGS_ENABLE_CARET_BROWSING_TITLE},
-    {"caretBrowsingSubtitle", IDS_SETTINGS_ENABLE_CARET_BROWSING_SUBTITLE},
+      {"moreFeaturesLink", IDS_SETTINGS_MORE_FEATURES_LINK},
+      {"a11yPageTitle", IDS_SETTINGS_ACCESSIBILITY},
+      {"a11yWebStore", IDS_SETTINGS_ACCESSIBILITY_WEB_STORE},
+      {"moreFeaturesLinkDescription",
+       IDS_SETTINGS_MORE_FEATURES_LINK_DESCRIPTION},
+      {"accessibleImageLabelsTitle",
+       IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_TITLE},
+      {"accessibleImageLabelsSubtitle",
+       IDS_SETTINGS_ACCESSIBLE_IMAGE_LABELS_SUBTITLE},
+      {"settingsSliderRoleDescription",
+       IDS_SETTINGS_SLIDER_MIN_MAX_ARIA_ROLE_DESCRIPTION},
+      {"caretBrowsingTitle", IDS_SETTINGS_ENABLE_CARET_BROWSING_TITLE},
+      {"caretBrowsingSubtitle", IDS_SETTINGS_ENABLE_CARET_BROWSING_SUBTITLE},
 #if BUILDFLAG(IS_CHROMEOS)
-    {"manageAccessibilityFeatures",
-     IDS_SETTINGS_ACCESSIBILITY_MANAGE_ACCESSIBILITY_FEATURES},
+      {"manageAccessibilityFeatures",
+       IDS_SETTINGS_ACCESSIBILITY_MANAGE_ACCESSIBILITY_FEATURES},
 #else  // !BUILDFLAG(IS_CHROMEOS)
-    {"focusHighlightLabel",
-     IDS_SETTINGS_ACCESSIBILITY_FOCUS_HIGHLIGHT_DESCRIPTION},
+      {"focusHighlightLabel",
+       IDS_SETTINGS_ACCESSIBILITY_FOCUS_HIGHLIGHT_DESCRIPTION},
 #endif
 #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
-    {"overscrollHistoryNavigationTitle",
-     IDS_SETTINGS_OVERSCROLL_HISTORY_NAVIGATION_TITLE},
-    {"overscrollHistoryNavigationSubtitle",
-     IDS_SETTINGS_OVERSCROLL_HISTORY_NAVIGATION_SUBTITLE},
-    {"pdfOcrDownloadCompleteLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_COMPLETE},
-    {"pdfOcrDownloadErrorLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_ERROR},
-    {"pdfOcrDownloadProgressLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_PROGRESS},
-    {"pdfOcrDownloadingLabel", IDS_SETTINGS_PDF_OCR_DOWNLOADING},
-    {"pdfOcrTitle", IDS_SETTINGS_PDF_OCR_TITLE},
-    {"pdfOcrSubtitle", IDS_SETTINGS_PDF_OCR_SUBTITLE},
+      {"overscrollHistoryNavigationTitle",
+       IDS_SETTINGS_OVERSCROLL_HISTORY_NAVIGATION_TITLE},
+      {"overscrollHistoryNavigationSubtitle",
+       IDS_SETTINGS_OVERSCROLL_HISTORY_NAVIGATION_SUBTITLE},
+      {"pdfOcrDownloadCompleteLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_COMPLETE},
+      {"pdfOcrDownloadErrorLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_ERROR},
+      {"pdfOcrDownloadProgressLabel", IDS_SETTINGS_PDF_OCR_DOWNLOAD_PROGRESS},
+      {"pdfOcrDownloadingLabel", IDS_SETTINGS_PDF_OCR_DOWNLOADING},
+      {"pdfOcrTitle", IDS_SETTINGS_PDF_OCR_TITLE},
+      {"pdfOcrSubtitle", IDS_SETTINGS_PDF_OCR_SUBTITLE},
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
@@ -330,25 +333,25 @@
 void AddAboutStrings(content::WebUIDataSource* html_source, Profile* profile) {
   // Top level About Page strings.
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"aboutProductLogoAlt", IDS_SHORT_PRODUCT_LOGO_ALT_TEXT},
+      {"aboutProductLogoAlt", IDS_SHORT_PRODUCT_LOGO_ALT_TEXT},
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    {"aboutReportAnIssue", IDS_SETTINGS_ABOUT_PAGE_REPORT_AN_ISSUE},
-    {"aboutPrivacyPolicy", IDS_SETTINGS_ABOUT_PAGE_PRIVACY_POLICY},
+      {"aboutReportAnIssue", IDS_SETTINGS_ABOUT_PAGE_REPORT_AN_ISSUE},
+      {"aboutPrivacyPolicy", IDS_SETTINGS_ABOUT_PAGE_PRIVACY_POLICY},
 #endif
-    {"aboutRelaunch", IDS_SETTINGS_ABOUT_PAGE_RELAUNCH},
-    {"aboutUpgradeCheckStarted", IDS_SETTINGS_ABOUT_UPGRADE_CHECK_STARTED},
-    {"aboutUpgradeRelaunch", IDS_SETTINGS_UPGRADE_SUCCESSFUL_RELAUNCH},
-    {"aboutUpgradeUpdating", IDS_SETTINGS_UPGRADE_UPDATING},
-    {"aboutUpgradeUpdatingPercent", IDS_SETTINGS_UPGRADE_UPDATING_PERCENT},
-    {"aboutGetHelpUsingChrome", IDS_SETTINGS_GET_HELP_USING_CHROME},
-    {"aboutPageTitle", IDS_SETTINGS_ABOUT_PROGRAM},
-    {"aboutProductTitle", IDS_PRODUCT_NAME},
-    {"aboutLearnMoreUpdatingErrors",
-     IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_UPDATE_ERRORS},
-    {"aboutLearnMoreSystemRequirements",
-     IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_SYSTEM_REQUIREMENTS},
+      {"aboutRelaunch", IDS_SETTINGS_ABOUT_PAGE_RELAUNCH},
+      {"aboutUpgradeCheckStarted", IDS_SETTINGS_ABOUT_UPGRADE_CHECK_STARTED},
+      {"aboutUpgradeRelaunch", IDS_SETTINGS_UPGRADE_SUCCESSFUL_RELAUNCH},
+      {"aboutUpgradeUpdating", IDS_SETTINGS_UPGRADE_UPDATING},
+      {"aboutUpgradeUpdatingPercent", IDS_SETTINGS_UPGRADE_UPDATING_PERCENT},
+      {"aboutGetHelpUsingChrome", IDS_SETTINGS_GET_HELP_USING_CHROME},
+      {"aboutPageTitle", IDS_SETTINGS_ABOUT_PROGRAM},
+      {"aboutProductTitle", IDS_PRODUCT_NAME},
+      {"aboutLearnMoreUpdatingErrors",
+       IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_UPDATE_ERRORS},
+      {"aboutLearnMoreSystemRequirements",
+       IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_SYSTEM_REQUIREMENTS},
 #if BUILDFLAG(IS_MAC)
-    {"aboutLearnMoreUpdating", IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_UPDATING},
+      {"aboutLearnMoreUpdating", IDS_SETTINGS_ABOUT_PAGE_LEARN_MORE_UPDATING},
 #endif
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
@@ -406,58 +409,58 @@
 void AddAppearanceStrings(content::WebUIDataSource* html_source,
                           Profile* profile) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"appearancePageTitle", IDS_SETTINGS_APPEARANCE},
-    {"customWebAddress", IDS_SETTINGS_CUSTOM_WEB_ADDRESS},
-    {"enterCustomWebAddress", IDS_SETTINGS_ENTER_CUSTOM_WEB_ADDRESS},
-    {"homeButtonDisabled", IDS_SETTINGS_HOME_BUTTON_DISABLED},
-    {"themes", IDS_SETTINGS_THEMES},
-    {"chromeColors", IDS_SETTINGS_CHROME_COLORS},
-    {"colorSchemeMode", IDS_SETTINGS_COLOR_SCHEME_MODE},
-    {"lightMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_LIGHT_LABEL},
-    {"darkMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_DARK_LABEL},
-    {"systemMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_SYSTEM_LABEL},
-    {"showHomeButton", IDS_SETTINGS_SHOW_HOME_BUTTON},
-    {"showBookmarksBar", IDS_SETTINGS_SHOW_BOOKMARKS_BAR},
-    {"showHoverCardImages", IDS_SETTINGS_SHOW_HOVER_CARD_IMAGES},
-    {"sidePanel", IDS_SETTINGS_SIDE_PANEL},
-    {"homePageNtp", IDS_SETTINGS_HOME_PAGE_NTP},
-    {"changeHomePage", IDS_SETTINGS_CHANGE_HOME_PAGE},
-    {"themesGalleryUrl", IDS_THEMES_GALLERY_URL},
-    {"chooseFromWebStore", IDS_SETTINGS_WEB_STORE},
-    {"pageZoom", IDS_SETTINGS_PAGE_ZOOM_LABEL},
-    {"fontSize", IDS_SETTINGS_FONT_SIZE_LABEL},
-    {"customizeFonts", IDS_SETTINGS_CUSTOMIZE_FONTS},
-    {"standardFont", IDS_SETTINGS_STANDARD_FONT_LABEL},
-    {"serifFont", IDS_SETTINGS_SERIF_FONT_LABEL},
-    {"sansSerifFont", IDS_SETTINGS_SANS_SERIF_FONT_LABEL},
-    {"fixedWidthFont", IDS_SETTINGS_FIXED_WIDTH_FONT_LABEL},
-    {"mathFont", IDS_SETTINGS_MATH_FONT_LABEL},
-    {"minimumFont", IDS_SETTINGS_MINIMUM_FONT_SIZE_LABEL},
-    {"tiny", IDS_SETTINGS_TINY_FONT_SIZE},
-    {"huge", IDS_SETTINGS_HUGE_FONT_SIZE},
-    {"sidePanelAlignLeft", IDS_SETTINGS_SIDE_PANEL_ALIGN_LEFT},
-    {"sidePanelAlignRight", IDS_SETTINGS_SIDE_PANEL_ALIGN_RIGHT},
+      {"appearancePageTitle", IDS_SETTINGS_APPEARANCE},
+      {"customWebAddress", IDS_SETTINGS_CUSTOM_WEB_ADDRESS},
+      {"enterCustomWebAddress", IDS_SETTINGS_ENTER_CUSTOM_WEB_ADDRESS},
+      {"homeButtonDisabled", IDS_SETTINGS_HOME_BUTTON_DISABLED},
+      {"themes", IDS_SETTINGS_THEMES},
+      {"chromeColors", IDS_SETTINGS_CHROME_COLORS},
+      {"colorSchemeMode", IDS_SETTINGS_COLOR_SCHEME_MODE},
+      {"lightMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_LIGHT_LABEL},
+      {"darkMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_DARK_LABEL},
+      {"systemMode", IDS_NTP_CUSTOMIZE_CHROME_COLOR_SCHEME_MODE_SYSTEM_LABEL},
+      {"showHomeButton", IDS_SETTINGS_SHOW_HOME_BUTTON},
+      {"showBookmarksBar", IDS_SETTINGS_SHOW_BOOKMARKS_BAR},
+      {"showHoverCardImages", IDS_SETTINGS_SHOW_HOVER_CARD_IMAGES},
+      {"sidePanel", IDS_SETTINGS_SIDE_PANEL},
+      {"homePageNtp", IDS_SETTINGS_HOME_PAGE_NTP},
+      {"changeHomePage", IDS_SETTINGS_CHANGE_HOME_PAGE},
+      {"themesGalleryUrl", IDS_THEMES_GALLERY_URL},
+      {"chooseFromWebStore", IDS_SETTINGS_WEB_STORE},
+      {"pageZoom", IDS_SETTINGS_PAGE_ZOOM_LABEL},
+      {"fontSize", IDS_SETTINGS_FONT_SIZE_LABEL},
+      {"customizeFonts", IDS_SETTINGS_CUSTOMIZE_FONTS},
+      {"standardFont", IDS_SETTINGS_STANDARD_FONT_LABEL},
+      {"serifFont", IDS_SETTINGS_SERIF_FONT_LABEL},
+      {"sansSerifFont", IDS_SETTINGS_SANS_SERIF_FONT_LABEL},
+      {"fixedWidthFont", IDS_SETTINGS_FIXED_WIDTH_FONT_LABEL},
+      {"mathFont", IDS_SETTINGS_MATH_FONT_LABEL},
+      {"minimumFont", IDS_SETTINGS_MINIMUM_FONT_SIZE_LABEL},
+      {"tiny", IDS_SETTINGS_TINY_FONT_SIZE},
+      {"huge", IDS_SETTINGS_HUGE_FONT_SIZE},
+      {"sidePanelAlignLeft", IDS_SETTINGS_SIDE_PANEL_ALIGN_LEFT},
+      {"sidePanelAlignRight", IDS_SETTINGS_SIDE_PANEL_ALIGN_RIGHT},
 #if BUILDFLAG(IS_LINUX)
-    {"gtkTheme", IDS_SETTINGS_GTK_THEME},
-    {"useGtkTheme", IDS_SETTINGS_USE_GTK_THEME},
-    {"qtTheme", IDS_SETTINGS_QT_THEME},
-    {"useQtTheme", IDS_SETTINGS_USE_QT_THEME},
-    {"classicTheme", IDS_SETTINGS_CLASSIC_THEME},
-    {"useClassicTheme", IDS_SETTINGS_USE_CLASSIC_THEME},
+      {"gtkTheme", IDS_SETTINGS_GTK_THEME},
+      {"useGtkTheme", IDS_SETTINGS_USE_GTK_THEME},
+      {"qtTheme", IDS_SETTINGS_QT_THEME},
+      {"useQtTheme", IDS_SETTINGS_USE_QT_THEME},
+      {"classicTheme", IDS_SETTINGS_CLASSIC_THEME},
+      {"useClassicTheme", IDS_SETTINGS_USE_CLASSIC_THEME},
 #else
-    {"resetToDefaultTheme", IDS_SETTINGS_RESET_TO_DEFAULT_THEME},
+      {"resetToDefaultTheme", IDS_SETTINGS_RESET_TO_DEFAULT_THEME},
 #endif
 #if BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"showWindowDecorations", IDS_SHOW_WINDOW_DECORATIONS},
+      {"showWindowDecorations", IDS_SHOW_WINDOW_DECORATIONS},
 #endif
 #if BUILDFLAG(IS_MAC)
-    {"tabsToLinks", IDS_SETTINGS_TABS_TO_LINKS_PREF},
-    {"warnBeforeQuitting", IDS_SETTINGS_WARN_BEFORE_QUITTING_PREF},
+      {"tabsToLinks", IDS_SETTINGS_TABS_TO_LINKS_PREF},
+      {"warnBeforeQuitting", IDS_SETTINGS_WARN_BEFORE_QUITTING_PREF},
 #endif
-    {"readerMode", IDS_SETTINGS_READER_MODE},
-    {"readerModeDescription", IDS_SETTINGS_READER_MODE_DESCRIPTION},
-    {"themeManagedDialogTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
-    {"themeManagedDialogBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
+      {"readerMode", IDS_SETTINGS_READER_MODE},
+      {"readerModeDescription", IDS_SETTINGS_READER_MODE_DESCRIPTION},
+      {"themeManagedDialogTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
+      {"themeManagedDialogBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -831,83 +834,87 @@
 void AddLanguagesStrings(content::WebUIDataSource* html_source,
                          Profile* profile) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"languagesPageTitle", IDS_SETTINGS_LANGUAGES_PAGE_TITLE},
+      {"languagesPageTitle", IDS_SETTINGS_LANGUAGES_PAGE_TITLE},
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-    {"languagesCardTitle", IDS_SETTINGS_LANGUAGES_CARD_TITLE},
-    {"searchLanguages", IDS_SETTINGS_LANGUAGE_SEARCH},
-    {"languagesExpandA11yLabel",
-     IDS_SETTINGS_LANGUAGES_EXPAND_ACCESSIBILITY_LABEL},
-    {"preferredLanguagesDesc", IDS_SETTINGS_LANGUAGES_PREFERRED_LANGUAGES_DESC},
-    {"moveToTop", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_TO_TOP},
-    {"moveUp", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_UP},
-    {"moveDown", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_DOWN},
-    {"removeLanguage", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_REMOVE},
-    {"addLanguages", IDS_SETTINGS_LANGUAGES_LANGUAGES_ADD},
-    {"addLanguagesDialogTitle", IDS_SETTINGS_LANGUAGES_MANAGE_LANGUAGES_TITLE},
+      {"languagesCardTitle", IDS_SETTINGS_LANGUAGES_CARD_TITLE},
+      {"searchLanguages", IDS_SETTINGS_LANGUAGE_SEARCH},
+      {"languagesExpandA11yLabel",
+       IDS_SETTINGS_LANGUAGES_EXPAND_ACCESSIBILITY_LABEL},
+      {"preferredLanguagesDesc",
+       IDS_SETTINGS_LANGUAGES_PREFERRED_LANGUAGES_DESC},
+      {"moveToTop", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_TO_TOP},
+      {"moveUp", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_UP},
+      {"moveDown", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_MOVE_DOWN},
+      {"removeLanguage", IDS_SETTINGS_LANGUAGES_LANGUAGES_LIST_REMOVE},
+      {"addLanguages", IDS_SETTINGS_LANGUAGES_LANGUAGES_ADD},
+      {"addLanguagesDialogTitle",
+       IDS_SETTINGS_LANGUAGES_MANAGE_LANGUAGES_TITLE},
 #if BUILDFLAG(IS_WIN)
-    {"isDisplayedInThisLanguage",
-     IDS_SETTINGS_LANGUAGES_IS_DISPLAYED_IN_THIS_LANGUAGE},
-    {"displayInThisLanguage", IDS_SETTINGS_LANGUAGES_DISPLAY_IN_THIS_LANGUAGE},
+      {"isDisplayedInThisLanguage",
+       IDS_SETTINGS_LANGUAGES_IS_DISPLAYED_IN_THIS_LANGUAGE},
+      {"displayInThisLanguage",
+       IDS_SETTINGS_LANGUAGES_DISPLAY_IN_THIS_LANGUAGE},
 #endif
-    {"offerToEnableTranslate",
-     IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_TRANSLATE},
-    {"offerToEnableTranslateSublabel",
-     IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_TRANSLATE_SUBLABEL},
-    {"noLanguagesAdded", IDS_SETTINGS_LANGUAGES_NO_LANGUAGES_ADDED},
-    {"addLanguageAriaLabel", IDS_SETTINGS_LANGUAGES_ADD_ARIA_LABEL},
-    {"removeAutomaticLanguageAriaLabel",
-     IDS_SETTINGS_LANGUAGES_REMOVE_AUTOMATIC_ARIA_LABEL},
-    {"removeNeverLanguageAriaLabel",
-     IDS_SETTINGS_LANGUAGES_REMOVE_NEVER_ARIA_LABEL},
-    {"translatePageTitle", IDS_SETTINGS_TRANSLATE_PAGE_TITLE},
-    {"targetLanguageLabel", IDS_SETTINGS_TARGET_TRANSLATE_LABEL},
-    {"automaticallyTranslateLanguages",
-     IDS_SETTINGS_LANGUAGES_AUTOMATIC_TRANSLATE},
-    {"addAutomaticallyTranslateLanguagesAriaLabel",
-     IDS_SETTINGS_LANGUAGES_AUTOMATIC_TRANSLATE_ADD_ARIA_LABEL},
-    {"neverTranslateLanguages", IDS_SETTINGS_LANGUAGES_NEVER_LANGUAGES},
-    {"addNeverTranslateLanguagesAriaLabel",
-     IDS_SETTINGS_LANGUAGES_NEVER_TRANSLATE_ADD_ARIA_LABEL},
-    {"translateTargetLabel", IDS_SETTINGS_LANGUAGES_TRANSLATE_TARGET},
-    {"spellCheckTitle", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE},
-    {"spellCheckBasicLabel", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_BASIC_LABEL},
-    {"spellCheckEnhancedLabel",
-     IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_LABEL},
-    {"spellCheckEnhancedDescription",
-     IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_DESCRIPTION},
-    {"offerToEnableSpellCheck",
-     IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_SPELL_CHECK},
-    // Managed dialog strings:
-    {"languageManagedDialogTitle", IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE},
-    {"languageManagedDialogBody", IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY},
+      {"offerToEnableTranslate",
+       IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_TRANSLATE},
+      {"offerToEnableTranslateSublabel",
+       IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_TRANSLATE_SUBLABEL},
+      {"noLanguagesAdded", IDS_SETTINGS_LANGUAGES_NO_LANGUAGES_ADDED},
+      {"addLanguageAriaLabel", IDS_SETTINGS_LANGUAGES_ADD_ARIA_LABEL},
+      {"removeAutomaticLanguageAriaLabel",
+       IDS_SETTINGS_LANGUAGES_REMOVE_AUTOMATIC_ARIA_LABEL},
+      {"removeNeverLanguageAriaLabel",
+       IDS_SETTINGS_LANGUAGES_REMOVE_NEVER_ARIA_LABEL},
+      {"translatePageTitle", IDS_SETTINGS_TRANSLATE_PAGE_TITLE},
+      {"targetLanguageLabel", IDS_SETTINGS_TARGET_TRANSLATE_LABEL},
+      {"automaticallyTranslateLanguages",
+       IDS_SETTINGS_LANGUAGES_AUTOMATIC_TRANSLATE},
+      {"addAutomaticallyTranslateLanguagesAriaLabel",
+       IDS_SETTINGS_LANGUAGES_AUTOMATIC_TRANSLATE_ADD_ARIA_LABEL},
+      {"neverTranslateLanguages", IDS_SETTINGS_LANGUAGES_NEVER_LANGUAGES},
+      {"addNeverTranslateLanguagesAriaLabel",
+       IDS_SETTINGS_LANGUAGES_NEVER_TRANSLATE_ADD_ARIA_LABEL},
+      {"translateTargetLabel", IDS_SETTINGS_LANGUAGES_TRANSLATE_TARGET},
+      {"spellCheckTitle", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_TITLE},
+      {"spellCheckBasicLabel", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_BASIC_LABEL},
+      {"spellCheckEnhancedLabel",
+       IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_LABEL},
+      {"spellCheckEnhancedDescription",
+       IDS_SETTINGS_LANGUAGES_SPELL_CHECK_ENHANCED_DESCRIPTION},
+      {"offerToEnableSpellCheck",
+       IDS_SETTINGS_LANGUAGES_OFFER_TO_ENABLE_SPELL_CHECK},
+      // Managed dialog strings:
+      {"languageManagedDialogTitle",
+       IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_TITLE},
+      {"languageManagedDialogBody", IDS_SETTINGS_LANGUAGES_MANAGED_DIALOG_BODY},
 #if !BUILDFLAG(IS_MAC)
-    {"spellCheckDisabledReason",
-     IDS_SETTING_LANGUAGES_SPELL_CHECK_DISABLED_REASON},
-    {"spellCheckLanguagesListTitle",
-     IDS_SETTINGS_LANGUAGES_SPELL_CHECK_LANGUAGES_LIST_TITLE},
-    {"manageSpellCheck", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_MANAGE},
-    {"editDictionaryPageTitle", IDS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_TITLE},
-    {"addDictionaryWordLabel", IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD},
-    {"addDictionaryWordButton",
-     IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_BUTTON},
-    {"addDictionaryWordDuplicateError",
-     IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_DUPLICATE_ERROR},
-    {"addDictionaryWordLengthError",
-     IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_LENGTH_ERROR},
-    {"deleteDictionaryWordButton",
-     IDS_SETTINGS_LANGUAGES_DELETE_DICTIONARY_WORD_BUTTON},
-    {"customDictionaryWords", IDS_SETTINGS_LANGUAGES_DICTIONARY_WORDS},
-    {"noCustomDictionaryWordsFound",
-     IDS_SETTINGS_LANGUAGES_DICTIONARY_WORDS_NONE},
-    {"languagesDictionaryDownloadError",
-     IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED},
-    {"languagesDictionaryDownloadErrorHelp",
-     IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED_HELP},
+      {"spellCheckDisabledReason",
+       IDS_SETTING_LANGUAGES_SPELL_CHECK_DISABLED_REASON},
+      {"spellCheckLanguagesListTitle",
+       IDS_SETTINGS_LANGUAGES_SPELL_CHECK_LANGUAGES_LIST_TITLE},
+      {"manageSpellCheck", IDS_SETTINGS_LANGUAGES_SPELL_CHECK_MANAGE},
+      {"editDictionaryPageTitle", IDS_SETTINGS_LANGUAGES_EDIT_DICTIONARY_TITLE},
+      {"addDictionaryWordLabel", IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD},
+      {"addDictionaryWordButton",
+       IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_BUTTON},
+      {"addDictionaryWordDuplicateError",
+       IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_DUPLICATE_ERROR},
+      {"addDictionaryWordLengthError",
+       IDS_SETTINGS_LANGUAGES_ADD_DICTIONARY_WORD_LENGTH_ERROR},
+      {"deleteDictionaryWordButton",
+       IDS_SETTINGS_LANGUAGES_DELETE_DICTIONARY_WORD_BUTTON},
+      {"customDictionaryWords", IDS_SETTINGS_LANGUAGES_DICTIONARY_WORDS},
+      {"noCustomDictionaryWordsFound",
+       IDS_SETTINGS_LANGUAGES_DICTIONARY_WORDS_NONE},
+      {"languagesDictionaryDownloadError",
+       IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED},
+      {"languagesDictionaryDownloadErrorHelp",
+       IDS_SETTINGS_LANGUAGES_DICTIONARY_DOWNLOAD_FAILED_HELP},
 #endif
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    {"openChromeOSLanguagesSettingsLabel",
-     IDS_SETTINGS_LANGUAGES_OPEN_CHROME_OS_SETTINGS_LABEL},
+      {"openChromeOSLanguagesSettingsLabel",
+       IDS_SETTINGS_LANGUAGES_OPEN_CHROME_OS_SETTINGS_LABEL},
 #endif
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
@@ -950,13 +957,15 @@
   // If |autofill_manager| is not available, then don't show toggle switch.
   autofill::ContentAutofillDriverFactory* autofill_driver_factory =
       autofill::ContentAutofillDriverFactory::FromWebContents(web_contents);
-  if (!autofill_driver_factory)
+  if (!autofill_driver_factory) {
     return false;
+  }
   autofill::ContentAutofillDriver* autofill_driver =
       autofill_driver_factory->DriverForFrame(
           web_contents->GetPrimaryMainFrame());
-  if (!autofill_driver)
+  if (!autofill_driver) {
     return false;
+  }
 
   // Show the toggle switch only if FIDO authentication is available. Once
   // returned, this decision may be overridden (from true to false) by the
@@ -1186,7 +1195,8 @@
       "manageCreditCardsLabel",
       l10n_util::GetStringFUTF16(
           IDS_SETTINGS_PAYMENTS_MANAGE_CREDIT_CARDS,
-          base::UTF8ToUTF16(autofill::payments::GetManageInstrumentsUrl().spec())));
+          base::UTF8ToUTF16(
+              autofill::payments::GetManageInstrumentsUrl().spec())));
   html_source->AddString("managePaymentMethodsUrl",
                          autofill::payments::GetManageInstrumentsUrl().spec());
   html_source->AddString("addressesAndPaymentMethodsLearnMoreURL",
@@ -1372,62 +1382,64 @@
 
 void AddPersonalizationOptionsStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"urlKeyedAnonymizedDataCollection",
-     IDS_SETTINGS_ENABLE_URL_KEYED_ANONYMIZED_DATA_COLLECTION},
-    {"urlKeyedAnonymizedDataCollectionDesc",
-     IDS_SETTINGS_ENABLE_URL_KEYED_ANONYMIZED_DATA_COLLECTION_DESC},
-    {"spellingPref", IDS_SETTINGS_SPELLING_PREF},
+      {"urlKeyedAnonymizedDataCollection",
+       IDS_SETTINGS_ENABLE_URL_KEYED_ANONYMIZED_DATA_COLLECTION},
+      {"urlKeyedAnonymizedDataCollectionDesc",
+       IDS_SETTINGS_ENABLE_URL_KEYED_ANONYMIZED_DATA_COLLECTION_DESC},
+      {"spellingPref", IDS_SETTINGS_SPELLING_PREF},
 #if !BUILDFLAG(IS_CHROMEOS)
-    {"signinAllowedTitle", IDS_SETTINGS_SIGNIN_ALLOWED},
-    {"signinAllowedDescription", IDS_SETTINGS_SIGNIN_ALLOWED_DESC},
+      {"signinAllowedTitle", IDS_SETTINGS_SIGNIN_ALLOWED},
+      {"signinAllowedDescription", IDS_SETTINGS_SIGNIN_ALLOWED_DESC},
 #endif
-    {"enablePersonalizationLogging", IDS_SETTINGS_ENABLE_LOGGING_PREF},
-    {"enablePersonalizationLoggingDesc", IDS_SETTINGS_ENABLE_LOGGING_PREF_DESC},
-    {"spellingDescription", IDS_SETTINGS_SPELLING_PREF_DESC},
-    {"searchSuggestPrefDesc", IDS_SETTINGS_SUGGEST_PREF_DESC},
-    {"linkDoctorPref", IDS_SETTINGS_LINKDOCTOR_PREF},
-    {"linkDoctorPrefDesc", IDS_SETTINGS_LINKDOCTOR_PREF_DESC},
-    {"searchSuggestPref", IDS_SETTINGS_SUGGEST_PREF},
-    {"driveSuggestPref", IDS_SETTINGS_DRIVE_SUGGEST_PREF},
-    {"driveSuggestPrefDesc", IDS_SETTINGS_DRIVE_SUGGEST_PREF_DESC},
-    {"priceEmailNotificationsPref", IDS_PRICE_TRACKING_SETTINGS_TITLE},
-    {"priceEmailNotificationsPrefDesc",
-     IDS_PRICE_TRACKING_SETTINGS_EMAIL_DESCRIPTION},
-    {"pageContentLinkRowSublabelOn",
-     IDS_SETTINGS_PAGE_CONTENT_LINK_ROW_SUBLABEL_ON},
-    {"pageContentLinkRowSublabelOff",
-     IDS_SETTINGS_PAGE_CONTENT_LINK_ROW_SUBLABEL_OFF},
-    {"pageContentPageTitle", IDS_SETTINGS_PAGE_CONTENT_PAGE_TITLE},
-    {"pageContentToggleLabel", IDS_SETTINGS_PAGE_CONTENT_TOGGLE_LABEL},
-    {"pageContentToggleSublabel", IDS_SETTINGS_PAGE_CONTENT_TOGGLE_SUBLABEL},
-    {"pageContentWhenOnBulletOne",
-     IDS_SETTINGS_PAGE_CONTENT_WHEN_ON_BULLET_ONE},
-    {"pageContentThingsToConsiderBulletOne",
-     IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_ONE},
-    {"pageContentThingsToConsiderBulletTwo",
-     IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_TWO},
-    {"pageContentThingsToConsiderBulletThree",
-     IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_THREE},
+      {"enablePersonalizationLogging", IDS_SETTINGS_ENABLE_LOGGING_PREF},
+      {"enablePersonalizationLoggingDesc",
+       IDS_SETTINGS_ENABLE_LOGGING_PREF_DESC},
+      {"spellingDescription", IDS_SETTINGS_SPELLING_PREF_DESC},
+      {"searchSuggestPrefDesc", IDS_SETTINGS_SUGGEST_PREF_DESC},
+      {"linkDoctorPref", IDS_SETTINGS_LINKDOCTOR_PREF},
+      {"linkDoctorPrefDesc", IDS_SETTINGS_LINKDOCTOR_PREF_DESC},
+      {"searchSuggestPref", IDS_SETTINGS_SUGGEST_PREF},
+      {"driveSuggestPref", IDS_SETTINGS_DRIVE_SUGGEST_PREF},
+      {"driveSuggestPrefDesc", IDS_SETTINGS_DRIVE_SUGGEST_PREF_DESC},
+      {"priceEmailNotificationsPref", IDS_PRICE_TRACKING_SETTINGS_TITLE},
+      {"priceEmailNotificationsPrefDesc",
+       IDS_PRICE_TRACKING_SETTINGS_EMAIL_DESCRIPTION},
+      {"pageContentLinkRowSublabelOn",
+       IDS_SETTINGS_PAGE_CONTENT_LINK_ROW_SUBLABEL_ON},
+      {"pageContentLinkRowSublabelOff",
+       IDS_SETTINGS_PAGE_CONTENT_LINK_ROW_SUBLABEL_OFF},
+      {"pageContentPageTitle", IDS_SETTINGS_PAGE_CONTENT_PAGE_TITLE},
+      {"pageContentToggleLabel", IDS_SETTINGS_PAGE_CONTENT_TOGGLE_LABEL},
+      {"pageContentToggleSublabel", IDS_SETTINGS_PAGE_CONTENT_TOGGLE_SUBLABEL},
+      {"pageContentWhenOnBulletOne",
+       IDS_SETTINGS_PAGE_CONTENT_WHEN_ON_BULLET_ONE},
+      {"pageContentThingsToConsiderBulletOne",
+       IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_ONE},
+      {"pageContentThingsToConsiderBulletTwo",
+       IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_TWO},
+      {"pageContentThingsToConsiderBulletThree",
+       IDS_SETTINGS_PAGE_CONTENT_THINGS_TO_CONSIDER_BULLET_THREE},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 }
 
 void AddBrowserSyncPageStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"peopleSignInSyncPagePromptSecondaryWithAccount",
-     IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
-    {"peopleSignInSyncPagePromptSecondaryWithNoAccount",
-     IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
-    {"bookmarksCheckboxLabel", IDS_SETTINGS_BOOKMARKS_CHECKBOX_LABEL},
-    {"readingListCheckboxLabel", IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL},
-    {"cancelSync", IDS_SETTINGS_SYNC_SETTINGS_CANCEL_SYNC},
-    {"syncSetupCancelDialogTitle", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_TITLE},
-    {"syncSetupCancelDialogBody", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_BODY},
-    {"personalizeGoogleServicesTitle",
-     IDS_SETTINGS_PERSONALIZE_GOOGLE_SERVICES_TITLE},
-    {"themeCheckboxLabel", IDS_SETTINGS_THEME_CHECKBOX_LABEL},
+      {"peopleSignInSyncPagePromptSecondaryWithAccount",
+       IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
+      {"peopleSignInSyncPagePromptSecondaryWithNoAccount",
+       IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
+      {"bookmarksCheckboxLabel", IDS_SETTINGS_BOOKMARKS_CHECKBOX_LABEL},
+      {"readingListCheckboxLabel", IDS_SETTINGS_READING_LIST_CHECKBOX_LABEL},
+      {"cancelSync", IDS_SETTINGS_SYNC_SETTINGS_CANCEL_SYNC},
+      {"syncSetupCancelDialogTitle",
+       IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_TITLE},
+      {"syncSetupCancelDialogBody", IDS_SETTINGS_SYNC_SETUP_CANCEL_DIALOG_BODY},
+      {"personalizeGoogleServicesTitle",
+       IDS_SETTINGS_PERSONALIZE_GOOGLE_SERVICES_TITLE},
+      {"themeCheckboxLabel", IDS_SETTINGS_THEME_CHECKBOX_LABEL},
 #if BUILDFLAG(IS_CHROMEOS)
-    {"browserSyncFeatureLabel", IDS_BROWSER_SETTINGS_SYNC_FEATURE_LABEL},
+      {"browserSyncFeatureLabel", IDS_BROWSER_SETTINGS_SYNC_FEATURE_LABEL},
 #endif
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
@@ -1470,85 +1482,86 @@
 
 void AddSyncControlsStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"autofillCheckboxLabel", IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL},
-    {"historyCheckboxLabel", IDS_SETTINGS_HISTORY_CHECKBOX_LABEL},
-    {"extensionsCheckboxLabel", IDS_SETTINGS_EXTENSIONS_CHECKBOX_LABEL},
-    {"openTabsCheckboxLabel", IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL},
-    {"savedTabGroupsCheckboxLabel",
-     IDS_SETTINGS_SAVED_TAB_GROUPS_CHECKBOX_LABEL},
-    {"wifiConfigurationsCheckboxLabel",
-     IDS_SETTINGS_WIFI_CONFIGURATIONS_CHECKBOX_LABEL},
-    {"syncEverythingCheckboxLabel",
-     IDS_SETTINGS_SYNC_EVERYTHING_CHECKBOX_LABEL},
-    {"appCheckboxLabel", IDS_SETTINGS_APPS_CHECKBOX_LABEL},
+      {"autofillCheckboxLabel", IDS_SETTINGS_AUTOFILL_CHECKBOX_LABEL},
+      {"historyCheckboxLabel", IDS_SETTINGS_HISTORY_CHECKBOX_LABEL},
+      {"extensionsCheckboxLabel", IDS_SETTINGS_EXTENSIONS_CHECKBOX_LABEL},
+      {"openTabsCheckboxLabel", IDS_SETTINGS_OPEN_TABS_CHECKBOX_LABEL},
+      {"savedTabGroupsCheckboxLabel",
+       IDS_SETTINGS_SAVED_TAB_GROUPS_CHECKBOX_LABEL},
+      {"wifiConfigurationsCheckboxLabel",
+       IDS_SETTINGS_WIFI_CONFIGURATIONS_CHECKBOX_LABEL},
+      {"syncEverythingCheckboxLabel",
+       IDS_SETTINGS_SYNC_EVERYTHING_CHECKBOX_LABEL},
+      {"appCheckboxLabel", IDS_SETTINGS_APPS_CHECKBOX_LABEL},
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"appCheckboxSublabel", IDS_SETTINGS_APPS_CHECKBOX_SUBLABEL},
+      {"appCheckboxSublabel", IDS_SETTINGS_APPS_CHECKBOX_SUBLABEL},
 #endif
-    {"paymentsCheckboxLabel", IDS_SYNC_DATATYPE_PAYMENTS},
-    {"nonPersonalizedServicesSectionLabel",
-     IDS_SETTINGS_NON_PERSONALIZED_SERVICES_SECTION_LABEL},
-    {"customizeSyncLabel", IDS_SETTINGS_CUSTOMIZE_SYNC},
-    {"syncData", IDS_SETTINGS_SYNC_DATA},
+      {"paymentsCheckboxLabel", IDS_SYNC_DATATYPE_PAYMENTS},
+      {"nonPersonalizedServicesSectionLabel",
+       IDS_SETTINGS_NON_PERSONALIZED_SERVICES_SECTION_LABEL},
+      {"customizeSyncLabel", IDS_SETTINGS_CUSTOMIZE_SYNC},
+      {"syncData", IDS_SETTINGS_SYNC_DATA},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 }
 
 void AddPeopleStrings(content::WebUIDataSource* html_source, Profile* profile) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    // Top level people strings:
-    {"peopleSignInPromptSecondaryWithAccount",
-     IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
-    {"peopleSignInPromptSecondaryWithNoAccount",
-     IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
-    {"peoplePageTitle", IDS_SETTINGS_PEOPLE},
-    {"syncSettingsSavedToast", IDS_SETTINGS_SYNC_SETTINGS_SAVED_TOAST_LABEL},
-    {"peopleSignInPrompt", IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT},
-    {"manageGoogleAccount", IDS_SETTINGS_MANAGE_GOOGLE_ACCOUNT},
-    {"syncAndNonPersonalizedServices",
-     IDS_SETTINGS_SYNC_SYNC_AND_NON_PERSONALIZED_SERVICES},
+      // Top level people strings:
+      {"peopleSignInPromptSecondaryWithAccount",
+       IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
+      {"peopleSignInPromptSecondaryWithNoAccount",
+       IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT_SECONDARY_WITH_ACCOUNT},
+      {"peoplePageTitle", IDS_SETTINGS_PEOPLE},
+      {"syncSettingsSavedToast", IDS_SETTINGS_SYNC_SETTINGS_SAVED_TOAST_LABEL},
+      {"peopleSignInPrompt", IDS_SETTINGS_PEOPLE_SIGN_IN_PROMPT},
+      {"manageGoogleAccount", IDS_SETTINGS_MANAGE_GOOGLE_ACCOUNT},
+      {"syncAndNonPersonalizedServices",
+       IDS_SETTINGS_SYNC_SYNC_AND_NON_PERSONALIZED_SERVICES},
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-    {"accountManagerSubMenuLabel", IDS_SETTINGS_ACCOUNT_MANAGER_SUBMENU_LABEL},
+      {"accountManagerSubMenuLabel",
+       IDS_SETTINGS_ACCOUNT_MANAGER_SUBMENU_LABEL},
 #else
-    {"editPerson", IDS_SETTINGS_CUSTOMIZE_PROFILE},
-    {"profileNameAndPicture", IDS_SETTINGS_CUSTOMIZE_YOUR_CHROME_PROFILE},
+      {"editPerson", IDS_SETTINGS_CUSTOMIZE_PROFILE},
+      {"profileNameAndPicture", IDS_SETTINGS_CUSTOMIZE_YOUR_CHROME_PROFILE},
 #endif
 
   // Manage profile strings:
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
-    {"showShortcutLabel", IDS_SETTINGS_PROFILE_SHORTCUT_TOGGLE_LABEL},
-    {"nameInputLabel", IDS_SETTINGS_PROFILE_NAME_INPUT_LABEL},
-    {"nameYourProfile", IDS_SETTING_NAME_YOUR_PROFILE},
-    {"pickThemeColor", IDS_SETTINGS_PICK_A_THEME_COLOR},
-    {"pickAvatar", IDS_SETTINGS_PICK_AN_AVATAR},
-    {"createShortcutTitle", IDS_SETTINGS_CREATE_SHORTCUT},
-    {"createShortcutSubtitle", IDS_SETTINGS_CREATE_SHORTCUT_SUBTITLE},
+      {"showShortcutLabel", IDS_SETTINGS_PROFILE_SHORTCUT_TOGGLE_LABEL},
+      {"nameInputLabel", IDS_SETTINGS_PROFILE_NAME_INPUT_LABEL},
+      {"nameYourProfile", IDS_SETTING_NAME_YOUR_PROFILE},
+      {"pickThemeColor", IDS_SETTINGS_PICK_A_THEME_COLOR},
+      {"pickAvatar", IDS_SETTINGS_PICK_AN_AVATAR},
+      {"createShortcutTitle", IDS_SETTINGS_CREATE_SHORTCUT},
+      {"createShortcutSubtitle", IDS_SETTINGS_CREATE_SHORTCUT_SUBTITLE},
 
-    // Color picker strings:
-    {"colorsContainerLabel", IDS_NTP_THEMES_CONTAINER_LABEL},
-    {"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
-    {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
-    {"defaultThemeLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
-    {"greyDefaultColorName", IDS_NTP_CUSTOMIZE_GREY_DEFAULT_LABEL},
-    {"hueSliderTitle", IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_TITLE},
-    {"mainColorName", IDS_NTP_CUSTOMIZE_MAIN_COLOR_LABEL},
-    {"managedColorsBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
-    {"managedColorsTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
-    {"themesContainerLabel", IDS_SETTINGS_PICK_A_THEME_COLOR},
-    {"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
-    {"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
+      // Color picker strings:
+      {"colorsContainerLabel", IDS_NTP_THEMES_CONTAINER_LABEL},
+      {"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
+      {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
+      {"defaultThemeLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
+      {"greyDefaultColorName", IDS_NTP_CUSTOMIZE_GREY_DEFAULT_LABEL},
+      {"hueSliderTitle", IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_TITLE},
+      {"mainColorName", IDS_NTP_CUSTOMIZE_MAIN_COLOR_LABEL},
+      {"managedColorsBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
+      {"managedColorsTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
+      {"themesContainerLabel", IDS_SETTINGS_PICK_A_THEME_COLOR},
+      {"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
+      {"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
 
-    // Managed theme dialog strings:
-    {"themeManagedDialogTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
-    {"themeManagedDialogBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
+      // Managed theme dialog strings:
+      {"themeManagedDialogTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
+      {"themeManagedDialogBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
 #endif
-    {"deleteProfileWarningExpandA11yLabel",
-     IDS_SETTINGS_SYNC_DISCONNECT_EXPAND_ACCESSIBILITY_LABEL},
-    {"deleteProfileWarningWithCountsSingular",
-     IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITH_COUNTS_SINGULAR},
-    {"deleteProfileWarningWithCountsPlural",
-     IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITH_COUNTS_PLURAL},
-    {"deleteProfileWarningWithoutCounts",
-     IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITHOUT_COUNTS},
+      {"deleteProfileWarningExpandA11yLabel",
+       IDS_SETTINGS_SYNC_DISCONNECT_EXPAND_ACCESSIBILITY_LABEL},
+      {"deleteProfileWarningWithCountsSingular",
+       IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITH_COUNTS_SINGULAR},
+      {"deleteProfileWarningWithCountsPlural",
+       IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITH_COUNTS_PLURAL},
+      {"deleteProfileWarningWithoutCounts",
+       IDS_SETTINGS_SYNC_DISCONNECT_DELETE_PROFILE_WARNING_WITHOUT_COUNTS},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -2123,116 +2136,117 @@
 
 void AddPrivacyGuideStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"privacyGuideLabel", IDS_SETTINGS_PRIVACY_GUIDE_LABEL},
-    {"privacyGuideSublabel", IDS_SETTINGS_PRIVACY_GUIDE_SUBLABEL},
-    {"privacyGuidePromoHeader", IDS_SETTINGS_PRIVACY_GUIDE_PROMO_HEADER},
-    {"privacyGuidePromoBody", IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY},
-    {"privacyGuidePromoStartButton",
-     IDS_SETTINGS_PRIVACY_GUIDE_PROMO_START_BUTTON},
-    {"privacyGuideBackToSettingsAriaLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_BACK_TO_SETTINGS_ARIA_LABEL},
-    {"privacyGuideBackToSettingsAriaRoleDescription",
-     IDS_SETTINGS_PRIVACY_GUIDE_BACK_TO_SETTINGS_ARIA_ROLE_DESC},
-    {"privacyGuideBackButton", IDS_SETTINGS_PRIVACY_GUIDE_BACK_BUTTON},
-    {"privacyGuideSteps", IDS_SETTINGS_PRIVACY_GUIDE_STEPS},
-    {"privacyGuideNextButton", IDS_SETTINGS_PRIVACY_GUIDE_NEXT_BUTTON},
-    {"privacyGuideFeatureDescriptionHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_FEATURE_DESCRIPTION_HEADER},
-    {"privacyGuideThingsToConsider",
-     IDS_SETTINGS_PRIVACY_GUIDE_THINGS_TO_CONSIDER},
-    {"privacyGuideWelcomeCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_HEADER},
-    {"privacyGuideWelcomeCardSubHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_SUB_HEADER},
-    {"privacyGuideCompletionCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_HEADER},
-    {"privacyGuideCompletionCardSubHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER},
-    {"privacyGuideCompletionCardSubHeaderNoLinks",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER_NO_LINKS},
-    {"privacyGuideCompletionCardLeaveButton",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_LEAVE_BUTTON},
-    {"privacyGuideCompletionCardPrivacySandboxLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_LABEL},
-    {"privacyGuideCompletionCardPrivacySandboxSubLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL},
-    {"privacyGuideCompletionCardWaaLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_LABEL},
-    {"privacyGuideCompletionCardWaaSubLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL},
-    {"privacyGuideMsbbCardHeader", IDS_SETTINGS_PRIVACY_GUIDE_MSBB_CARD_HEADER},
-    {"privacyGuideMsbbFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION1},
-    {"privacyGuideMsbbFeatureDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION2},
-    {"privacyGuideMsbbFeatureDescription3",
-     IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION3},
-    {"privacyGuideMsbbPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION1},
-    {"privacyGuideMsbbPrivacyDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2},
-    {"privacyGuideHistorySyncCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_CARD_HEADER},
-    {"privacyGuideHistorySyncSettingLabel",
-     IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_SETTING_LABEL},
-    {"privacyGuideHistorySyncFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION1},
-    {"privacyGuideHistorySyncFeatureDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION2},
-    {"privacyGuideHistorySyncPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_PRIVACY_DESCRIPTION1},
-    {"privacyGuideCookiesCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_HEADER},
-    {"privacyGuideCookiesCardBlockTpcIncognitoSubheader",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_SUBHEADER},
-    {"privacyGuideCookiesCardBlockTpcIncognitoFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION1},
-    {"privacyGuideCookiesCardBlockTpcIncognitoFeatureDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION2},
-    {"privacyGuideCookiesCardBlockTpcIncognitoPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION1},
-    {"privacyGuideCookiesCardBlockTpcIncognitoPrivacyDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION2},
-    {"privacyGuideCookiesCardBlockTpcSubheader",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_SUBHEADER},
-    {"privacyGuideCookiesCardBlockTpcFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION1},
-    {"privacyGuideCookiesCardBlockTpcFeatureDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION2},
-    {"privacyGuideCookiesCardBlockTpcPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_PRIVACY_DESCRIPTION1},
-    {"privacyGuideSafeBrowsingCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_HEADER},
-    {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION1},
-    {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION2},
-    {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription3",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION3},
-    {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION1},
-    {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2},
+      {"privacyGuideLabel", IDS_SETTINGS_PRIVACY_GUIDE_LABEL},
+      {"privacyGuideSublabel", IDS_SETTINGS_PRIVACY_GUIDE_SUBLABEL},
+      {"privacyGuidePromoHeader", IDS_SETTINGS_PRIVACY_GUIDE_PROMO_HEADER},
+      {"privacyGuidePromoBody", IDS_SETTINGS_PRIVACY_GUIDE_PROMO_BODY},
+      {"privacyGuidePromoStartButton",
+       IDS_SETTINGS_PRIVACY_GUIDE_PROMO_START_BUTTON},
+      {"privacyGuideBackToSettingsAriaLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_BACK_TO_SETTINGS_ARIA_LABEL},
+      {"privacyGuideBackToSettingsAriaRoleDescription",
+       IDS_SETTINGS_PRIVACY_GUIDE_BACK_TO_SETTINGS_ARIA_ROLE_DESC},
+      {"privacyGuideBackButton", IDS_SETTINGS_PRIVACY_GUIDE_BACK_BUTTON},
+      {"privacyGuideSteps", IDS_SETTINGS_PRIVACY_GUIDE_STEPS},
+      {"privacyGuideNextButton", IDS_SETTINGS_PRIVACY_GUIDE_NEXT_BUTTON},
+      {"privacyGuideFeatureDescriptionHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_FEATURE_DESCRIPTION_HEADER},
+      {"privacyGuideThingsToConsider",
+       IDS_SETTINGS_PRIVACY_GUIDE_THINGS_TO_CONSIDER},
+      {"privacyGuideWelcomeCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_HEADER},
+      {"privacyGuideWelcomeCardSubHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_WELCOME_CARD_SUB_HEADER},
+      {"privacyGuideCompletionCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_HEADER},
+      {"privacyGuideCompletionCardSubHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER},
+      {"privacyGuideCompletionCardSubHeaderNoLinks",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_SUB_HEADER_NO_LINKS},
+      {"privacyGuideCompletionCardLeaveButton",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_LEAVE_BUTTON},
+      {"privacyGuideCompletionCardPrivacySandboxLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_LABEL},
+      {"privacyGuideCompletionCardPrivacySandboxSubLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_PRIVACY_SANDBOX_SUB_LABEL},
+      {"privacyGuideCompletionCardWaaLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_LABEL},
+      {"privacyGuideCompletionCardWaaSubLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_COMPLETION_CARD_WAA_SUB_LABEL},
+      {"privacyGuideMsbbCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_CARD_HEADER},
+      {"privacyGuideMsbbFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION1},
+      {"privacyGuideMsbbFeatureDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION2},
+      {"privacyGuideMsbbFeatureDescription3",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_FEATURE_DESCRIPTION3},
+      {"privacyGuideMsbbPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION1},
+      {"privacyGuideMsbbPrivacyDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_MSBB_PRIVACY_DESCRIPTION2},
+      {"privacyGuideHistorySyncCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_CARD_HEADER},
+      {"privacyGuideHistorySyncSettingLabel",
+       IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_SETTING_LABEL},
+      {"privacyGuideHistorySyncFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION1},
+      {"privacyGuideHistorySyncFeatureDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_FEATURE_DESCRIPTION2},
+      {"privacyGuideHistorySyncPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_HISTORY_SYNC_PRIVACY_DESCRIPTION1},
+      {"privacyGuideCookiesCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_HEADER},
+      {"privacyGuideCookiesCardBlockTpcIncognitoSubheader",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_SUBHEADER},
+      {"privacyGuideCookiesCardBlockTpcIncognitoFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION1},
+      {"privacyGuideCookiesCardBlockTpcIncognitoFeatureDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_FEATURE_DESCRIPTION2},
+      {"privacyGuideCookiesCardBlockTpcIncognitoPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION1},
+      {"privacyGuideCookiesCardBlockTpcIncognitoPrivacyDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_INCOGNITO_PRIVACY_DESCRIPTION2},
+      {"privacyGuideCookiesCardBlockTpcSubheader",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_SUBHEADER},
+      {"privacyGuideCookiesCardBlockTpcFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION1},
+      {"privacyGuideCookiesCardBlockTpcFeatureDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_FEATURE_DESCRIPTION2},
+      {"privacyGuideCookiesCardBlockTpcPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_COOKIES_CARD_BLOCK_TPC_PRIVACY_DESCRIPTION1},
+      {"privacyGuideSafeBrowsingCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_HEADER},
+      {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION1},
+      {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION2},
+      {"privacyGuideSafeBrowsingCardEnhancedProtectionPrivacyDescription3",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_ENHANCED_PROTECTION_PRIVACY_DESCRIPTION3},
+      {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION1},
+      {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2},
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription2Proxy",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2_PROXY},
+      {"privacyGuideSafeBrowsingCardStandardProtectionFeatureDescription2Proxy",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_FEATURE_DESCRIPTION2_PROXY},
 #endif
-    {"privacyGuideSafeBrowsingCardStandardProtectionPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1},
+      {"privacyGuideSafeBrowsingCardStandardProtectionPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1},
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-    {"privacyGuideSafeBrowsingCardStandardProtectionPrivacyDescription1Proxy",
-     IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1_PROXY},
+      {"privacyGuideSafeBrowsingCardStandardProtectionPrivacyDescription1Proxy",
+       IDS_SETTINGS_PRIVACY_GUIDE_SAFE_BROWSING_CARD_STANDARD_PROTECTION_PRIVACY_DESCRIPTION1_PROXY},
 #endif
-    {"privacyGuideSearchSuggestionsCardHeader",
-     IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_CARD_HEADER},
-    {"privacyGuideSearchSuggestionsFeatureDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_FEATURE_DESCRIPTION1},
-    {"privacyGuideSearchSuggestionsPrivacyDescription1",
-     IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION1},
-    {"privacyGuideSearchSuggestionsPrivacyDescription2",
-     IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION2},
-    {"privacyGuideSearchSuggestionsPrivacyDescription3",
-     IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION3},
+      {"privacyGuideSearchSuggestionsCardHeader",
+       IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_CARD_HEADER},
+      {"privacyGuideSearchSuggestionsFeatureDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_FEATURE_DESCRIPTION1},
+      {"privacyGuideSearchSuggestionsPrivacyDescription1",
+       IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION1},
+      {"privacyGuideSearchSuggestionsPrivacyDescription2",
+       IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION2},
+      {"privacyGuideSearchSuggestionsPrivacyDescription3",
+       IDS_SETTINGS_PRIVACY_GUIDE_SEARCH_SUGGESTIONS_PRIVACY_DESCRIPTION3},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 }
@@ -2397,21 +2411,22 @@
 
 void AddSearchStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"searchEnginesManage", IDS_SETTINGS_SEARCH_MANAGE_SEARCH_ENGINES},
-    {"searchEnginesManageSiteSearch",
-     IDS_SETTINGS_SEARCH_MANAGE_SEARCH_ENGINES_AND_SITE_SEARCH},
-    {"searchPageTitle", IDS_SETTINGS_SEARCH},
-    {"searchExplanation", IDS_SETTINGS_SEARCH_EXPLANATION},
-    {"searchExplanationLearnMoreA11yLabel",
-     IDS_SETTINGS_SEARCH_EXPLANATION_ACCESSIBILITY_LABEL},
-    {"searchEngineChoiceEntryPointSubtitle",
-     IDS_SEARCH_ENGINE_CHOICE_SETTINGS_ENTRY_POINT_SUBTITLE},
-    {"searchEnginesChange",
-     IDS_SEARCH_ENGINE_CHOICE_SETTINGS_CHANGE_DEFAULT_ENGINE},
-    {"searchEnginesSettingsDialogSubtitle",
-     IDS_SEARCH_ENGINE_CHOICE_SETTINGS_SUBTITLE},
-    {"searchEnginesSetAsDefaultButton", IDS_SEARCH_ENGINE_CHOICE_BUTTON_TITLE},
-    {"searchEnginesCancelButton", IDS_CANCEL},
+      {"searchEnginesManage", IDS_SETTINGS_SEARCH_MANAGE_SEARCH_ENGINES},
+      {"searchEnginesManageSiteSearch",
+       IDS_SETTINGS_SEARCH_MANAGE_SEARCH_ENGINES_AND_SITE_SEARCH},
+      {"searchPageTitle", IDS_SETTINGS_SEARCH},
+      {"searchExplanation", IDS_SETTINGS_SEARCH_EXPLANATION},
+      {"searchExplanationLearnMoreA11yLabel",
+       IDS_SETTINGS_SEARCH_EXPLANATION_ACCESSIBILITY_LABEL},
+      {"searchEngineChoiceEntryPointSubtitle",
+       IDS_SEARCH_ENGINE_CHOICE_SETTINGS_ENTRY_POINT_SUBTITLE},
+      {"searchEnginesChange",
+       IDS_SEARCH_ENGINE_CHOICE_SETTINGS_CHANGE_DEFAULT_ENGINE},
+      {"searchEnginesSettingsDialogSubtitle",
+       IDS_SEARCH_ENGINE_CHOICE_SETTINGS_SUBTITLE},
+      {"searchEnginesSetAsDefaultButton",
+       IDS_SEARCH_ENGINE_CHOICE_BUTTON_TITLE},
+      {"searchEnginesCancelButton", IDS_CANCEL},
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
 
@@ -3423,18 +3438,18 @@
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
 void AddSystemStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
-    {"systemPageTitle", IDS_SETTINGS_SYSTEM},
+      {"systemPageTitle", IDS_SETTINGS_SYSTEM},
 #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"backgroundAppsLabel", IDS_SETTINGS_SYSTEM_BACKGROUND_APPS_LABEL},
+      {"backgroundAppsLabel", IDS_SETTINGS_SYSTEM_BACKGROUND_APPS_LABEL},
 #endif
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"hardwareAccelerationLabel",
-     IDS_SETTINGS_SYSTEM_HARDWARE_ACCELERATION_LABEL},
-    {"proxySettingsLabel", IDS_SETTINGS_SYSTEM_PROXY_SETTINGS_LABEL},
+      {"hardwareAccelerationLabel",
+       IDS_SETTINGS_SYSTEM_HARDWARE_ACCELERATION_LABEL},
+      {"proxySettingsLabel", IDS_SETTINGS_SYSTEM_PROXY_SETTINGS_LABEL},
 #endif
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-    {"useAshProxyLabel", IDS_SETTINGS_SYSTEM_USE_ASH_PROXY_LABEL},
-    {"usesAshProxyLabel", IDS_SETTINGS_SYSTEM_USES_ASH_PROXY_LABEL},
+      {"useAshProxyLabel", IDS_SETTINGS_SYSTEM_USE_ASH_PROXY_LABEL},
+      {"usesAshProxyLabel", IDS_SETTINGS_SYSTEM_USES_ASH_PROXY_LABEL},
 #endif
   };
   html_source->AddLocalizedStrings(kLocalizedStrings);
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
index f41ec4e..d0e0b5f8 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/customize_chrome_ui.cc
@@ -108,6 +108,9 @@
       {"defaultColorName", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
       {"greyDefaultColorName", IDS_NTP_CUSTOMIZE_GREY_DEFAULT_LABEL},
       {"hueSliderTitle", IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_TITLE},
+      {"hueSliderDeleteTitle", IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_TITLE},
+      {"hueSliderDeleteA11yLabel",
+       IDS_NTP_CUSTOMIZE_COLOR_HUE_SLIDER_DELETE_A11Y_LABEL},
       {"mainColorName", IDS_NTP_CUSTOMIZE_MAIN_COLOR_LABEL},
       {"managedColorsTitle", IDS_NTP_THEME_MANAGED_DIALOG_TITLE},
       {"managedColorsBody", IDS_NTP_THEME_MANAGED_DIALOG_BODY},
@@ -162,8 +165,6 @@
        IDS_NTP_WALLPAPER_SEARCH_REQUEST_THROTTLED_TITLE},
       {"tryAgain", IDS_NTP_WALLPAPER_SEARCH_TRY_AGAIN_CTA},
       {"wallpaperSearchHistoryHeader", IDS_NTP_WALLPAPER_SEARCH_HISTORY_HEADER},
-      {"wallpaperSearchHistoryTileTitle",
-       IDS_NTP_WALLPAPER_SEARCH_HISTORY_TILE_TITLE},
       {"wallpaperSearchMoodLabel", IDS_NTP_WALLPAPER_SEARCH_MOOD_LABEL},
       {"wallpaperSearchMoodDefaultOptionLabel",
        IDS_NTP_WALLPAPER_SEARCH_MOOD_DEFAULT_OPTION_LABEL},
@@ -194,7 +195,17 @@
       {"wallpaperSearchLoadingA11yMessage",
        IDS_NTP_WALLPAPER_SEARCH_LOADING_A11Y_MESSAGE},
       {"wallpaperSearchSuccessA11yMessage",
-       IDS_NTP_WALLPAPER_SEARCH_SUCCESS_A11Y_MESSAGE}};
+       IDS_NTP_WALLPAPER_SEARCH_SUCCESS_A11Y_MESSAGE},
+      {"wallpaperSearchHistoryResultLabelNoDescriptor",
+       IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_NO_DESCRIPTOR},
+      {"wallpaperSearchHistoryResultLabel",
+       IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL},
+      {"wallpaperSearchHistoryResultLabelB",
+       IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_B},
+      {"wallpaperSearchHistoryResultLabelC",
+       IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTOR_C},
+      {"wallpaperSearchHistoryResultLabelBC",
+       IDS_NTP_WALLPAPER_SEARCH_HISTORY_RESULT_LABEL_WITH_DESCRIPTORS_B_AND_C}};
   source->AddLocalizedStrings(kLocalizedStrings);
 
   source->AddBoolean(
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search.mojom b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search.mojom
index 80165cb..58c5144 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search.mojom
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search.mojom
@@ -7,21 +7,28 @@
 import "mojo/public/mojom/base/token.mojom";
 import "skia/public/mojom/skcolor.mojom";
 
+// Subject descriptor. Each one is a category of subjects and its
+// list of possible values.
 struct DescriptorA {
   string category;
   array<string> labels;
 };
 
+// Style descriptor value, which includes a label and an image.
 struct DescriptorB {
   string label;
   string image_path;
 };
 
+// Color descriptor value of either SkColor or hue.
+// Hue is for custom colors and SkColor is for default colors.
 union DescriptorDValue {
   skia.mojom.SkColor color;
   float hue;
 };
 
+// Full list of possible subject, style, and mood descriptors for the
+// dropdowns in the Wallpaper Search UI.
 struct Descriptors {
   array<DescriptorA> descriptor_a;
   array<DescriptorB> descriptor_b;
@@ -31,6 +38,16 @@
 struct WallpaperSearchResult {
   mojo_base.mojom.Token id;
   string image;
+  ResultDescriptors? descriptors;
+};
+
+// A set of descriptors related to a single wallpaper search result or A
+// set of wallpaper search results.
+struct ResultDescriptors {
+  string? subject;
+  string? style;
+  string? mood;
+  DescriptorDValue? color;
 };
 
 // Must match |NtpWallpaperSearchError| in enums.xml, since this is used
@@ -78,13 +95,14 @@
 
   // Sets the history image identified by `result_id` as the current background
   // image.
-  SetBackgroundToHistoryImage(mojo_base.mojom.Token result_id);
+  SetBackgroundToHistoryImage(mojo_base.mojom.Token result_id,
+      ResultDescriptors descriptors);
 
   // Sets wallpaper search result identified by `result_id` to background image.
   // `time` is Unix epoch time stamp of when the user selected the respective
   // preview.
   SetBackgroundToWallpaperSearchResult(mojo_base.mojom.Token result_id,
-      double time);
+      double time, ResultDescriptors descriptors);
 
   // Triggers a call to |WallpaperSearchClient.SetHistory|.
   UpdateHistory();
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.cc
index 93fd617..f3c89c3 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.cc
@@ -275,7 +275,8 @@
 }
 
 void WallpaperSearchHandler::SetBackgroundToHistoryImage(
-    const base::Token& result_id) {
+    const base::Token& result_id,
+    side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors) {
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
       base::BindOnce(
@@ -283,16 +284,18 @@
           profile_->GetPath().AppendASCII(
               result_id.ToString() +
               chrome::kChromeUIUntrustedNewTabPageBackgroundFilename)),
-      base::BindOnce(&WallpaperSearchHandler::DecodeHistoryImage,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     base::BindOnce(&WallpaperSearchHandler::SelectHistoryImage,
-                                    weak_ptr_factory_.GetWeakPtr(), result_id,
-                                    base::ElapsedTimer())));
+      base::BindOnce(
+          &WallpaperSearchHandler::DecodeHistoryImage,
+          weak_ptr_factory_.GetWeakPtr(),
+          base::BindOnce(&WallpaperSearchHandler::SelectHistoryImage,
+                         weak_ptr_factory_.GetWeakPtr(), result_id,
+                         base::ElapsedTimer(), std::move(descriptors))));
 }
 
 void WallpaperSearchHandler::SetBackgroundToWallpaperSearchResult(
     const base::Token& result_id,
-    double time) {
+    double time,
+    side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors) {
   CHECK(base::Contains(wallpaper_search_results_, result_id));
   auto& [image_quality, render_time, bitmap] =
       wallpaper_search_results_[result_id];
@@ -305,6 +308,13 @@
     }
   }
   history_entry_ = std::make_unique<HistoryEntry>(result_id);
+  history_entry_->subject = descriptors->subject;
+  if (descriptors->style) {
+    history_entry_->style = descriptors->style;
+  }
+  if (descriptors->mood) {
+    history_entry_->mood = descriptors->mood;
+  }
   wallpaper_search_background_manager_->SelectLocalBackgroundImage(
       result_id, bitmap, base::ElapsedTimer());
 }
@@ -324,7 +334,7 @@
         base::BindOnce(
             &ReadFile,
             profile_->GetPath().AppendASCII(
-                entry.ToString() +
+                entry.id.ToString() +
                 chrome::kChromeUIUntrustedNewTabPageBackgroundFilename)),
         base::BindOnce(&WallpaperSearchHandler::DecodeHistoryImage,
                        weak_ptr_factory_.GetWeakPtr(),
@@ -335,7 +345,7 @@
                              std::move(barrier).Run(
                                  std::pair(image.AsBitmap(), id));
                            },
-                           barrier, entry)));
+                           barrier, entry.id)));
   }
 }
 
@@ -509,7 +519,7 @@
 }
 
 void WallpaperSearchHandler::OnHistoryDecoded(
-    std::vector<base::Token> history,
+    std::vector<HistoryEntry> history,
     std::vector<std::pair<SkBitmap, base::Token>> results) {
   std::vector<side_panel::customize_chrome::mojom::WallpaperSearchResultPtr>
       thumbnails;
@@ -518,7 +528,7 @@
   // O(n^2) but there should never be more than 6 in each vector.
   for (const auto& entry : history) {
     for (auto& [bitmap, id] : results) {
-      if (entry == id) {
+      if (entry.id == id) {
         auto dimensions =
             CalculateResizeDimensions(bitmap.width(), bitmap.height(), 100);
         SkBitmap small_bitmap = skia::ImageOperations::Resize(
@@ -533,6 +543,17 @@
               side_panel::customize_chrome::mojom::WallpaperSearchResult::New();
           thumbnail->image = base::Base64Encode(encoded);
           thumbnail->id = std::move(id);
+          if (entry.subject) {
+            thumbnail->descriptors =
+                side_panel::customize_chrome::mojom::ResultDescriptors::New();
+            thumbnail->descriptors->subject = *entry.subject;
+            if (entry.style) {
+              thumbnail->descriptors->style = *entry.style;
+            }
+            if (entry.mood) {
+              thumbnail->descriptors->mood = *entry.mood;
+            }
+          }
           thumbnails.push_back(std::move(thumbnail));
         }
         break;
@@ -542,10 +563,21 @@
   client_->SetHistory(std::move(thumbnails));
 }
 
-void WallpaperSearchHandler::SelectHistoryImage(const base::Token& id,
-                                                base::ElapsedTimer timer,
-                                                const gfx::Image& image) {
+void WallpaperSearchHandler::SelectHistoryImage(
+    const base::Token& id,
+    base::ElapsedTimer timer,
+    side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors,
+    const gfx::Image& image) {
   history_entry_ = std::make_unique<HistoryEntry>(id);
+  if (descriptors->subject) {
+    history_entry_->subject = descriptors->subject;
+  }
+  if (descriptors->style) {
+    history_entry_->style = descriptors->style;
+  }
+  if (descriptors->mood) {
+    history_entry_->mood = descriptors->mood;
+  }
   wallpaper_search_background_manager_->SelectHistoryImage(id, image,
                                                            std::move(timer));
 }
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.h b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.h
index 7affb55..04c4ade2 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.h
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler.h
@@ -75,9 +75,15 @@
       GetWallpaperSearchResultsCallback callback) override;
   void SetResultRenderTime(const std::vector<base::Token>& result_ids,
                            double time) override;
-  void SetBackgroundToHistoryImage(const base::Token& result_id) override;
-  void SetBackgroundToWallpaperSearchResult(const base::Token& result_id,
-                                            double time) override;
+  void SetBackgroundToHistoryImage(
+      const base::Token& result_id,
+      side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors)
+      override;
+  void SetBackgroundToWallpaperSearchResult(
+      const base::Token& result_id,
+      double time,
+      side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors)
+      override;
   void UpdateHistory() override;
   void SetUserFeedback(side_panel::customize_chrome::mojom::UserFeedback
                            selected_option) override;
@@ -96,7 +102,7 @@
                               std::unique_ptr<std::string> response_body);
   void OnDescriptorsJsonParsed(GetDescriptorsCallback callback,
                                data_decoder::DataDecoder::ValueOrError result);
-  void OnHistoryDecoded(std::vector<base::Token> history,
+  void OnHistoryDecoded(std::vector<HistoryEntry> history,
                         std::vector<std::pair<SkBitmap, base::Token>> results);
   void OnWallpaperSearchResultsRetrieved(
       GetWallpaperSearchResultsCallback callback,
@@ -109,9 +115,11 @@
       std::vector<
           std::pair<optimization_guide::proto::WallpaperSearchImageQuality*,
                     SkBitmap>> bitmaps);
-  void SelectHistoryImage(const base::Token& id,
-                          base::ElapsedTimer timer,
-                          const gfx::Image& image);
+  void SelectHistoryImage(
+      const base::Token& id,
+      base::ElapsedTimer timer,
+      side_panel::customize_chrome::mojom::ResultDescriptorsPtr descriptors,
+      const gfx::Image& image);
 
   raw_ptr<Profile> profile_;
   PrefChangeRegistrar pref_change_registrar_;
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
index e93b24f..f11dc221 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
@@ -89,7 +89,7 @@
  public:
   explicit MockWallpaperSearchBackgroundManager(Profile* profile)
       : WallpaperSearchBackgroundManager(profile) {}
-  MOCK_METHOD0(GetHistory, std::vector<base::Token>());
+  MOCK_METHOD0(GetHistory, std::vector<HistoryEntry>());
   MOCK_METHOD3(SelectHistoryImage,
                void(const base::Token&,
                     const gfx::Image&,
@@ -246,8 +246,12 @@
                       std::string(encoded.begin(), encoded.end()))));
 
   // Return test image from WallpaperSearchBackgroundManager::GetHistory().
-  std::vector<base::Token> history;
-  history.push_back(token);
+  std::vector<HistoryEntry> history;
+  HistoryEntry history_entry = HistoryEntry(token);
+  history_entry.subject = "foo";
+  history_entry.mood = "bar";
+  history_entry.style = "foobar";
+  history.push_back(history_entry);
   ON_CALL(mock_wallpaper_search_background_manager(), GetHistory())
       .WillByDefault(testing::Return(history));
 
@@ -270,6 +274,9 @@
       resized_bitmap, /*discard_transparency=*/false, &resized_encoded);
   EXPECT_EQ(history_images[0]->image, base::Base64Encode(resized_encoded));
   EXPECT_EQ(history_images[0]->id.ToString(), token.ToString());
+  EXPECT_EQ(history_images[0]->descriptors->subject, history_entry.subject);
+  EXPECT_EQ(history_images[0]->descriptors->mood, history_entry.mood);
+  EXPECT_EQ(history_images[0]->descriptors->style, history_entry.style);
 }
 
 TEST_F(WallpaperSearchHandlerTest,
@@ -1100,7 +1107,12 @@
               SaveCurrentBackgroundToHistory(_))
       .WillOnce(MoveArgAndReturn<0>(&history_entry_arg, token));
 
-  handler->SetBackgroundToHistoryImage(token);
+  side_panel::customize_chrome::mojom::ResultDescriptorsPtr result_descriptors =
+      side_panel::customize_chrome::mojom::ResultDescriptors::New();
+  result_descriptors->subject = "foo";
+  result_descriptors->mood = "bar";
+  result_descriptors->style = "foobar";
+  handler->SetBackgroundToHistoryImage(token, std::move(result_descriptors));
   task_environment().RunUntilIdle();
   task_environment().AdvanceClock(base::Milliseconds(321));
 
@@ -1121,6 +1133,9 @@
   // Check that the set theme is saved to history on destruction.
   handler.reset();
   EXPECT_EQ(token_arg.ToString(), history_entry_arg.id.ToString());
+  EXPECT_EQ("foo", history_entry_arg.subject);
+  EXPECT_EQ("bar", history_entry_arg.mood);
+  EXPECT_EQ("foobar", history_entry_arg.style);
 }
 
 TEST_F(WallpaperSearchHandlerTest, SetBackgroundToWallpaperSearchResult) {
@@ -1228,9 +1243,14 @@
       .WillOnce(
           DoAll(SaveArg<0>(&token), SaveArg<1>(&bitmap), MoveArg<2>(&timer)));
 
+  auto descriptors =
+      side_panel::customize_chrome::mojom::ResultDescriptors::New();
+  descriptors->subject = "foo";
   handler->SetBackgroundToWallpaperSearchResult(
-      images[1]->id, (base::Time::Now() + base::Milliseconds(123))
-                         .InMillisecondsFSinceUnixEpoch());
+      images[1]->id,
+      (base::Time::Now() + base::Milliseconds(123))
+          .InMillisecondsFSinceUnixEpoch(),
+      std::move(descriptors));
   task_environment().AdvanceClock(base::Milliseconds(123));
 
   // Check that the 2nd bitmap was selected by comparing color, since the
@@ -1287,6 +1307,7 @@
 
   // Set background saves on destruction.
   EXPECT_EQ(history_entry_arg.id, token);
+  EXPECT_EQ("foo", history_entry_arg.subject);
 }
 
 TEST_F(WallpaperSearchHandlerTest, SetUserFeedback) {
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 77685c41..548bd99 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1704801529-f6c8b28e53af17c7f2dbfd7fbc3324dad225b301.profdata
+chrome-android32-main-1704823116-46d9da59cab6c9fdc8f6fee5cdeed4b052215860.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index a281cfb..ae365138 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1704801529-aefc601a2f1a203e6a09da3a50367a3e9e3435d1.profdata
+chrome-android64-main-1704823116-4da12fdff8cc333a33be8dc1a830de6500f3c09e.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index c59db36..10592b9 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1704801529-64e88dc6c209b20543ac5caddeccd0c3861af5dc.profdata
+chrome-linux-main-1704823116-d2339317f2327666a71246c36a03121a84c75e92.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index a490179..53b859a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1704801529-1d02c6daa1f25a791a9995cff24ca607d0da4164.profdata
+chrome-win32-main-1704823116-248465c969595762dcf33890b5382929efff4a2f.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index f486d91..222462d 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1704801529-fba7705f4788080d70b6b27203577bbee631905d.profdata
+chrome-win64-main-1704823116-526eb7f4f62cc5ee2ad9864230ec58d07b6c3d77.profdata
diff --git a/chrome/common/compose/compose.mojom b/chrome/common/compose/compose.mojom
index b37ee92..100daea3 100644
--- a/chrome/common/compose/compose.mojom
+++ b/chrome/common/compose/compose.mojom
@@ -62,6 +62,8 @@
   string result;
   // Whether undo is possible to a prior state before this response.
   bool undo_available;
+  // Whether the response was generated on-device.
+  bool on_device_evaluation_used;
 };
 
 // An intermediate compose response, which is available before the final
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 5874f564..cd5f069 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -377,6 +377,12 @@
     "search.contextual_search_fully_opted_in";
 #endif  // BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_CHROMEOS)
+// Boolean pref recording whether cookie and data would be used only for
+// essential purposes.
+inline constexpr char kEssentialSearchEnabled[] = "essential_search_enabled";
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 #if BUILDFLAG(IS_MAC)
 // Boolean that indicates whether the browser should put up a confirmation
 // window when the user is attempting to quit. Only on Mac.
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 8d65ad73..d244404 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -6,6 +6,7 @@
 
 #include <functional>
 #include <memory>
+#include <string_view>
 #include <utility>
 
 #include "base/check_op.h"
@@ -1476,10 +1477,10 @@
   return prerender::NoStatePrefetchHelper::IsPrefetching(render_frame);
 }
 
-uint64_t ChromeContentRendererClient::VisitedLinkHash(const char* canonical_url,
-                                                      size_t length) {
+uint64_t ChromeContentRendererClient::VisitedLinkHash(
+    std::string_view canonical_url) {
   return chrome_observer_->visited_link_reader()->ComputeURLFingerprint(
-      canonical_url, length);
+      canonical_url);
 }
 
 bool ChromeContentRendererClient::IsLinkVisited(uint64_t link_hash) {
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index d5bc561..945cf55 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -11,6 +11,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/gtest_prod_util.h"
@@ -150,7 +151,7 @@
                        const url::Origin* initiator_origin,
                        GURL* new_url) override;
   bool IsPrefetchOnly(content::RenderFrame* render_frame) override;
-  uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override;
   bool IsLinkVisited(uint64_t link_hash) override;
   std::unique_ptr<blink::WebPrescientNetworking> CreatePrescientNetworking(
       content::RenderFrame* render_frame) override;
diff --git a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
index 7766bc3a..1a68f3c0 100644
--- a/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_classifier_browsertest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 
 #include "base/functional/bind.h"
@@ -44,7 +45,7 @@
   ~TestChromeContentRendererClient() override {}
   // Since visited_link_reader_ in ChromeContentRenderClient never get
   // initiated, overrides VisitedLinkedHash() function to prevent crashing.
-  uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override {
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override {
     return 0;
   }
 };
diff --git a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
index 4116066..0563ead5 100644
--- a/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
+++ b/chrome/renderer/safe_browsing/phishing_dom_feature_extractor_browsertest.cc
@@ -5,6 +5,7 @@
 #include "components/safe_browsing/content/renderer/phishing_classifier/phishing_dom_feature_extractor.h"
 
 #include <memory>
+#include <string_view>
 #include <unordered_map>
 
 #include "base/functional/bind.h"
@@ -132,7 +133,7 @@
   ~TestChromeContentRendererClient() override {}
   // Since visited_link_reader_ in ChromeContentRenderClient never get
   // initiated, overrides VisitedLinkedHash() function to prevent crashing.
-  uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override {
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override {
     return 0;
   }
 };
diff --git a/chrome/services/mac_notifications/mac_notification_provider_impl.mm b/chrome/services/mac_notifications/mac_notification_provider_impl.mm
index ed54169..e4fd4e4 100644
--- a/chrome/services/mac_notifications/mac_notification_provider_impl.mm
+++ b/chrome/services/mac_notifications/mac_notification_provider_impl.mm
@@ -38,9 +38,18 @@
     return;
   }
 
+// MacNotificationServiceNS implements the Chromium interface to the
+// NSUserNotificationCenter deprecated API. It is in the process of being
+// replaced by UNNotification, above, and warnings about its deprecation are not
+// helpful. https://crbug.com/1127306
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+
   service_ = std::make_unique<MacNotificationServiceNS>(
       std::move(service), std::move(handler),
       [NSUserNotificationCenter defaultUserNotificationCenter]);
+
+#pragma clang diagnostic pop
 }
 
 }  // namespace mac_notifications
diff --git a/chrome/services/sharing/nearby/platform/BUILD.gn b/chrome/services/sharing/nearby/platform/BUILD.gn
index a546c96..9219a12 100644
--- a/chrome/services/sharing/nearby/platform/BUILD.gn
+++ b/chrome/services/sharing/nearby/platform/BUILD.gn
@@ -148,6 +148,8 @@
     "//components/prefs:test_support",
     "//components/sync_preferences:test_support",
     "//components/user_manager:test_support",
+    "//device/bluetooth:deprecated_experimental_mojo",
+    "//device/bluetooth:mocks",
     "//mojo/public/cpp/bindings",
     "//services/network/public/mojom",
     "//testing/gtest",
diff --git a/chrome/services/sharing/nearby/platform/DEPS b/chrome/services/sharing/nearby/platform/DEPS
index 936d5a33..7b345370 100644
--- a/chrome/services/sharing/nearby/platform/DEPS
+++ b/chrome/services/sharing/nearby/platform/DEPS
@@ -4,7 +4,7 @@
 
 include_rules = [
   '+ash/constants/ash_features.h',
-  '+device/bluetooth/public',
+  '+device/bluetooth',
   '+services/network/public',
   '+third_party/abseil-cpp/absl/strings/string_view.h',
   '+third_party/abseil-cpp/absl/time/time.h',
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
index 6ac7b29..6c5f6fe 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.cc
@@ -4,13 +4,22 @@
 
 #include "chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h"
 
+#include "base/logging.h"
 #include "base/notreached.h"
 
 namespace nearby::chrome {
 
-BleV2GattClient::BleV2GattClient() = default;
+BleV2GattClient::BleV2GattClient(
+    mojo::PendingRemote<bluetooth::mojom::Device> device)
+    : remote_device_(std::move(device)) {
+  // TODO(b/311430390): For now, just tear down the connection on disconnect. In
+  // the future, this should call Disconnect.
+  remote_device_.reset_on_disconnect();
+}
 
-BleV2GattClient::~BleV2GattClient() = default;
+BleV2GattClient::~BleV2GattClient() {
+  Disconnect();
+}
 
 bool BleV2GattClient::DiscoverServiceAndCharacteristics(
     const Uuid& service_uuid,
@@ -55,8 +64,9 @@
 }
 
 void BleV2GattClient::Disconnect() {
-  // TODO(b/311430390): Implement this function.
-  NOTIMPLEMENTED();
+  // TODO(b/311430390): For now, just tear down the connection when we call
+  // Disconnect. In the future this should clean up state.
+  remote_device_.reset();
 }
 
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
index 951a9359..01aa40b 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h
@@ -5,13 +5,17 @@
 #ifndef CHROME_SERVICES_SHARING_NEARBY_PLATFORM_BLE_V2_GATT_CLIENT_H_
 #define CHROME_SERVICES_SHARING_NEARBY_PLATFORM_BLE_V2_GATT_CLIENT_H_
 
+#include "device/bluetooth/public/mojom/device.mojom.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/nearby/src/internal/platform/implementation/ble_v2.h"
 
 namespace nearby::chrome {
 
 class BleV2GattClient : public ::nearby::api::ble_v2::GattClient {
  public:
-  BleV2GattClient();
+  explicit BleV2GattClient(
+      mojo::PendingRemote<bluetooth::mojom::Device> device);
   ~BleV2GattClient() override;
 
   BleV2GattClient(const BleV2GattClient&) = delete;
@@ -36,6 +40,9 @@
       absl::AnyInvocable<void(absl::string_view value)>
           on_characteristic_changed_cb) override;
   void Disconnect() override;
+
+ private:
+  mojo::Remote<bluetooth::mojom::Device> remote_device_;
 };
 
 }  // namespace nearby::chrome
diff --git a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
index 85c29a34..79a68863 100644
--- a/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
+++ b/chrome/services/sharing/nearby/platform/ble_v2_gatt_client_unittest.cc
@@ -2,12 +2,42 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h"
+
 #include <memory>
 
+#include "base/run_loop.h"
 #include "base/test/task_environment.h"
-#include "chrome/services/sharing/nearby/platform/ble_v2_gatt_client.h"
+#include "device/bluetooth/device.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+const char kTestAddress[] = "11:22:33:44:55:66";
+
+class FakeBluetoothDevice
+    : public testing::NiceMock<device::MockBluetoothDevice> {
+ public:
+  FakeBluetoothDevice(device::MockBluetoothAdapter* adapter,
+                      const std::string& address)
+      : testing::NiceMock<device::MockBluetoothDevice>(adapter,
+                                                       /*bluetooth_class=*/0u,
+                                                       /*name=*/"Test Device",
+                                                       address,
+                                                       /*paired=*/true,
+                                                       /*connected=*/true) {}
+
+  FakeBluetoothDevice(const FakeBluetoothDevice&) = delete;
+  FakeBluetoothDevice& operator=(const FakeBluetoothDevice&) = delete;
+};
+
+}  // namespace
+
 namespace nearby::chrome {
 
 class BleV2GattClientTest : public testing::Test {
@@ -18,12 +48,43 @@
   BleV2GattClientTest& operator=(const BleV2GattClientTest&) = delete;
 
   void SetUp() override {
-    ble_v2_gatt_client_ = std::make_unique<BleV2GattClient>();
+    adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+
+    fake_device_ =
+        std::make_unique<FakeBluetoothDevice>(adapter_.get(), kTestAddress);
+
+    ON_CALL(*adapter_, GetDevice(kTestAddress))
+        .WillByDefault(Return(fake_device_.get()));
+
+    auto connection = std::make_unique<
+        testing::NiceMock<device::MockBluetoothGattConnection>>(adapter_,
+                                                                kTestAddress);
+
+    // TODO(b/316395226): We're creating a real Device object here, and relying
+    // on the underlying MockBluetoothDevice implementation to handle the test
+    // logic. This is likely to become unwieldy, and we should define and use a
+    // stubbed FakeDevice class instead.
+    mojo::PendingRemote<bluetooth::mojom::Device> pending_device;
+    bluetooth::Device::Create(adapter_, std::move(connection),
+                              pending_device.InitWithNewPipeAndPassReceiver());
+
+    ble_v2_gatt_client_ =
+        std::make_unique<BleV2GattClient>(std::move(pending_device));
+  }
+
+  void TearDown() override {
+    ble_v2_gatt_client_->Disconnect();
+
+    // TODO(b/316395226): Rework to avoid RunUntilIdle().
+    base::RunLoop().RunUntilIdle();
   }
 
  protected:
-  std::unique_ptr<BleV2GattClient> ble_v2_gatt_client_;
   base::test::TaskEnvironment task_environment_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
+  std::unique_ptr<FakeBluetoothDevice> fake_device_;
+  std::unique_ptr<BleV2GattClient> ble_v2_gatt_client_;
 };
 
 // TODO(b/311430390): Remove this skeleton test once other methods are
diff --git a/chrome/test/base/chromeos/crosier/demo_integration_test.cc b/chrome/test/base/chromeos/crosier/demo_integration_test.cc
index 1ba42be..b7658fc6 100644
--- a/chrome/test/base/chromeos/crosier/demo_integration_test.cc
+++ b/chrome/test/base/chromeos/crosier/demo_integration_test.cc
@@ -112,13 +112,7 @@
   ChromeOSIntegrationTestMixin chromeos_integration_test_mixin_{&mixin_host_};
 };
 
-// TODO(b/313557752): Fix the consistent failures on LACROS.
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#define MAYBE_NewTab DISABLED_NewTab
-#else
-#define MAYBE_NewTab NewTab
-#endif
-IN_PROC_BROWSER_TEST_F(DemoIntegrationTest, MAYBE_NewTab) {
+IN_PROC_BROWSER_TEST_F(DemoIntegrationTest, NewTab) {
   chrome_test_base_chromeos_crosier::TestInfo info;
   info.set_description(R"(
 This test verifies Chrome can launch and open version page.
diff --git a/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js
index d172887..6625421 100644
--- a/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js
+++ b/chrome/test/data/extensions/api_test/declarative_net_request/dynamic_rules/background.js
@@ -116,8 +116,9 @@
   async function largeRegexError() {
     await updateDynamicRules(
         {addRules: [createLargeRegexRuleWithID(5)]},
-        'Rule with id 5 specified a more complex regex than allowed as part ' +
-            'of the "regexFilter" key.');
+        'Rule with id 5 was skipped as the "regexFilter" value exceeded the ' +
+        '2KB memory limit when compiled. Learn more: ' +
+        'https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest#regex-rules');
 
     chrome.test.succeed();
   },
diff --git a/chrome/test/data/web_apps/page_with_app_title.html b/chrome/test/data/web_apps/page_with_app_title.html
new file mode 100644
index 0000000..4a0420706
--- /dev/null
+++ b/chrome/test/data/web_apps/page_with_app_title.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>A Web App</title>
+  <meta name="app-title" content="AppTitle">
+</head>
+<body>
+  <h1>Content</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/test/data/web_apps/page_without_app_title.html b/chrome/test/data/web_apps/page_without_app_title.html
new file mode 100644
index 0000000..5d25cda
--- /dev/null
+++ b/chrome/test/data/web_apps/page_without_app_title.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>A Web App</title>
+</head>
+<body>
+  <h1>Content</h1>
+</body>
+</html>
\ No newline at end of file
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
index 01fa93f..63ade66 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_app_browsertest.cc
@@ -220,7 +220,13 @@
 // implementations but with mocked out network handler helper classes.
 using PersonalizationAppBrowserTest = PersonalizationAppMochaTestBase;
 
-IN_PROC_BROWSER_TEST_F(PersonalizationAppBrowserTest, Main) {
+// TODO(crbug.com/1517028): Re-enable this test flakily failing on dbg builds.
+#if !defined(NDEBUG)
+#define MAYBE_Main DISABLED_Main
+#else
+#define MAYBE_Main Main
+#endif
+IN_PROC_BROWSER_TEST_F(PersonalizationAppBrowserTest, MAYBE_Main) {
   RunTestWithoutTestLoader(
       "chromeos/personalization_app/personalization_app_test.js",
       "runMochaSuite('main page')");
diff --git a/chrome/test/data/webui/chromeos/personalization_app/personalization_breadcrumb_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/personalization_breadcrumb_element_test.ts
index b10e9451..3704271 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/personalization_breadcrumb_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/personalization_breadcrumb_element_test.ts
@@ -437,7 +437,7 @@
     assertBreadcrumbs(breadcrumbContainer, [
       breadcrumbElement.i18n('wallpaperLabel'),
       breadcrumbElement.i18n('seaPenLabel'),
-      'Airbrushed',
+      'Airbrush',
     ]);
 
     const original = PersonalizationRouterElement.instance;
@@ -506,7 +506,7 @@
     const selectedElement =
         dropdownMenu.querySelectorAll('button[aria-selected=\'true\']');
     assertEquals(1, selectedElement.length);
-    assertEquals('Airbrushed', (selectedElement[0] as HTMLElement)!.innerText);
+    assertEquals('Airbrush', (selectedElement[0] as HTMLElement)!.innerText);
   });
 
   test('navigates with SeaPen dropdown', async () => {
diff --git a/chrome/test/data/webui/compose/compose_app_focus_test.ts b/chrome/test/data/webui/compose/compose_app_focus_test.ts
index ec1fb62..2e39f13 100644
--- a/chrome/test/data/webui/compose/compose_app_focus_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_focus_test.ts
@@ -33,8 +33,12 @@
   function mockResponse(
       result: string = 'some response',
       status: ComposeStatus = ComposeStatus.kOk): Promise<void> {
-    testProxy.remote.responseReceived(
-        {status: status, undoAvailable: false, result});
+    testProxy.remote.responseReceived({
+      status: status,
+      undoAvailable: false,
+      result,
+      onDeviceEvaluationUsed: false,
+    });
     return testProxy.remote.$.flushForTesting();
   }
 
@@ -122,6 +126,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: true,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     testProxy.setUndoResponse({
@@ -130,6 +135,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: false,
         result: 'some undone result',
+        onDeviceEvaluationUsed: false,
       },
       webuiState: JSON.stringify({
         input: 'my old input',
diff --git a/chrome/test/data/webui/compose/compose_app_test.ts b/chrome/test/data/webui/compose/compose_app_test.ts
index 4949c12..f5f1bf4 100644
--- a/chrome/test/data/webui/compose/compose_app_test.ts
+++ b/chrome/test/data/webui/compose/compose_app_test.ts
@@ -38,9 +38,10 @@
 
   function mockResponse(
       result: string = 'some response',
-      status: ComposeStatus = ComposeStatus.kOk): Promise<void> {
+      status: ComposeStatus = ComposeStatus.kOk,
+      onDeviceEvaluationUsed = false): Promise<void> {
     testProxy.remote.responseReceived(
-        {status: status, undoAvailable: false, result});
+        {status: status, undoAvailable: false, result, onDeviceEvaluationUsed});
     return testProxy.remote.$.flushForTesting();
   }
 
@@ -102,12 +103,21 @@
     assertFalse(isVisible(app.$.submitButton));
     assertTrue(app.$.textarea.readonly);
     assertTrue(isVisible(app.$.acceptButton));
+    assertFalse(isVisible(app.$.onDeviceUsedFooter));
 
     // Clicking on accept button calls acceptComposeResult.
     app.$.acceptButton.click();
     await testProxy.whenCalled('acceptComposeResult');
   });
 
+  test('OnlyOneErrorShows', async () => {
+    mockInput('x'.repeat(2501));
+    app.$.submitButton.click();
+    assertTrue(app.$.submitButton.disabled);
+    assertTrue(isVisible(app.$.textarea.$.tooLongError));
+    assertFalse(isVisible(app.$.textarea.$.tooShortError));
+  });
+
   test('AcceptButtonText', async () => {
     async function initializeNewAppWithTextSelectedState(textSelected: boolean):
         Promise<ComposeAppElement> {
@@ -281,6 +291,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: false,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     assertTrue(isVisible(appWithResult.$.resultContainer));
@@ -296,6 +307,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: true,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     assertFalse(appWithUndo.$.undoButton.disabled);
@@ -308,6 +320,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: false,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     assertTrue(isVisible(appWithResultAndLoading.$.loading));
@@ -325,6 +338,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: false,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     assertTrue(isVisible(appEditingPrompt.$.editTextarea));
@@ -543,6 +557,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: true,
         result: 'here is a result',
+        onDeviceEvaluationUsed: false,
       },
     });
     testProxy.setUndoResponse({
@@ -551,6 +566,7 @@
         status: ComposeStatus.kOk,
         undoAvailable: false,
         result: 'some undone result',
+        onDeviceEvaluationUsed: false,
       },
       webuiState: JSON.stringify({
         input: 'my old input',
@@ -599,7 +615,9 @@
     assertEquals(app.$.partialResultText.innerText.trim(), 'partial response');
 
     // The final response hides the partial response text.
-    await mockResponse();
+    await mockResponse(
+        'some response', ComposeStatus.kOk, /*onDeviceEvaluationUsed=*/ true);
     assertFalse(isVisible(app.$.partialResultText));
+    assertTrue(isVisible(app.$.onDeviceUsedFooter));
   });
 });
diff --git a/chrome/test/data/webui/privacy_sandbox/internals/BUILD.gn b/chrome/test/data/webui/privacy_sandbox/internals/BUILD.gn
index d9c33c7..621a485 100644
--- a/chrome/test/data/webui/privacy_sandbox/internals/BUILD.gn
+++ b/chrome/test/data/webui/privacy_sandbox/internals/BUILD.gn
@@ -5,7 +5,10 @@
 import("../../build_webui_tests.gni")
 
 build_webui_tests("build") {
-  files = [ "privacy_sandbox_internals_test.ts" ]
+  files = [
+    "privacy_sandbox_internals_test.ts",
+    "content_settings_test.ts",
+  ]
   ts_path_mappings = [ "chrome://privacy-sandbox-internals/*|" + rebase_path(
                            "$root_gen_dir/chrome/browser/resources/privacy_sandbox/internals/tsc/*",
                            target_gen_dir) ]
diff --git a/chrome/test/data/webui/privacy_sandbox/internals/content_settings_test.ts b/chrome/test/data/webui/privacy_sandbox/internals/content_settings_test.ts
new file mode 100644
index 0000000..4f06a55e
--- /dev/null
+++ b/chrome/test/data/webui/privacy_sandbox/internals/content_settings_test.ts
@@ -0,0 +1,103 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+import 'chrome://privacy-sandbox-internals/content_setting_pattern_source.js';
+
+import {ContentSettingPatternSourceElement} from 'chrome://privacy-sandbox-internals/content_setting_pattern_source.js';
+import {ContentSettingPatternSource, RuleMetaData, SessionModel} from 'chrome://privacy-sandbox-internals/content_settings.mojom-webui.js';
+import {PageHandler, PageHandlerInterface} from 'chrome://privacy-sandbox-internals/privacy_sandbox_internals.mojom-webui.js';
+import {Value} from 'chrome://resources/mojo/mojo/public/mojom/base/values.mojom-webui.js';
+import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
+suite('ContentSettingsElementTest', function() {
+  let pageHandler: PageHandlerInterface;
+
+  let element: ContentSettingPatternSourceElement;
+
+  suiteSetup(async function() {
+    await customElements.whenDefined('content-setting-pattern-source');
+  });
+
+  setup(function() {
+    pageHandler = PageHandler.getRemote();
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    element = document.createElement('content-setting-pattern-source');
+    document.body.appendChild(element);
+  });
+
+  const buildContentSettingPattern = async(
+      primaryPattern: string,
+      secondaryPattern: string): Promise<ContentSettingPatternSource> => {
+    const cs: ContentSettingPatternSource = {} as ContentSettingPatternSource;
+    const value: Value = {} as Value;
+    value.intValue = 0;
+    cs.settingValue = value;
+    cs.incognito = true;
+
+    cs.primaryPattern =
+        (await pageHandler.stringToContentSettingsPattern(primaryPattern))
+            .pattern;
+    cs.secondaryPattern =
+        (await pageHandler.stringToContentSettingsPattern(secondaryPattern))
+            .pattern;
+
+    // TODO(b/308167671): expand test coverage to these fields
+    const metadata: RuleMetaData = {} as RuleMetaData;
+    metadata.sessionModel = SessionModel.DURABLE;
+    metadata.lastModified = {
+      internalValue: BigInt(0),
+    };
+    metadata.lastUsed = {
+      internalValue: BigInt(0),
+    };
+    metadata.lastVisited = {
+      internalValue: BigInt(0),
+    };
+    metadata.expiration = {
+      internalValue: BigInt(0),
+    };
+    metadata.lifetime = {
+      microseconds: BigInt(0),
+    };
+
+    cs.metadata = metadata;
+    return cs;
+  };
+
+  const getField = (selector: string): Element => {
+    const field = element.shadowRoot!.querySelector(selector);
+    assertTrue(!!field);
+    return field;
+  };
+
+  const assertValue = (valueContainer: Element, s: string) => {
+    const valueElement = valueContainer.children[0];
+    assertTrue(!!valueElement);
+    const span = valueElement.shadowRoot!.querySelector('#value');
+    assertTrue(!!span);
+    assertEquals(span.textContent, s);
+  };
+
+  const assertTimestamp = (container: Element, s: string) => {
+    const mojoTs = container.querySelector('mojo-timestamp');
+    assertTrue(!!mojoTs);
+    assertEquals(mojoTs.getAttribute('ts'), s);
+  };
+
+  test('foo', async () => {
+    const cs = await buildContentSettingPattern(
+        'http://google.com', 'https://[*.]example.com:102');
+    await element.configure(pageHandler, cs);
+    assertEquals(
+        getField('.id-primary-pattern').textContent, 'http://google.com');
+    assertEquals(
+        getField('.id-secondary-pattern').textContent,
+        'https://[*.]example.com:102');
+    assertValue(getField('.id-incognito'), 'true');
+    assertValue(getField('.id-session-model'), '0');
+    assertTimestamp(getField('.id-last-modified'), '0');
+    assertTimestamp(getField('.id-last-used'), '0');
+    assertTimestamp(getField('.id-last-visited'), '0');
+    assertTimestamp(getField('.id-expiration'), '0');
+  });
+});
diff --git a/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_browsertest.cc b/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_browsertest.cc
index 853e29f..87a8264 100644
--- a/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_browsertest.cc
+++ b/chrome/test/data/webui/privacy_sandbox/internals/privacy_sandbox_internals_browsertest.cc
@@ -31,4 +31,9 @@
           "mocha.run();");
 }
 
+IN_PROC_BROWSER_TEST_F(PrivacySandboxInternalsMochaTest,
+                       ContentSettingsCustomElement) {
+  RunTest("privacy_sandbox/internals/content_settings_test.js", "mocha.run();");
+}
+
 }  // namespace
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
index 98a0de3..20e1f12a 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
@@ -171,6 +171,36 @@
           checkedMarkedColors[0]!.parentElement!.getAttribute('aria-current'),
           'true');
     });
+
+    test('unselects hue', async () => {
+      createWallpaperSearchElementWithDescriptors();
+      await flushTasks();
+      assertTrue(wallpaperSearchElement.$.deleteSelectedHueButton.hidden);
+
+      // Select a hue and verify delete button becomes visible.
+      wallpaperSearchElement.$.hueSlider.selectedHue = 10;
+      wallpaperSearchElement.$.hueSlider.dispatchEvent(
+          new Event('selected-hue-changed'));
+      await flushTasks();
+      assertFalse(wallpaperSearchElement.$.deleteSelectedHueButton.hidden);
+
+      // Click on delete button.
+      wallpaperSearchElement.$.deleteSelectedHueButton.click();
+      await flushTasks();
+
+      // Verify there are no checked colors.
+      assertEquals(
+          0,
+          wallpaperSearchElement.shadowRoot!
+              .querySelectorAll('#descriptorMenuD button [checked]')
+              .length);
+
+      // Verify submitting does not send a hue.
+      wallpaperSearchElement.$.submitButton.click();
+      await flushTasks();
+      assertEquals(1, handler.getCallCount('getWallpaperSearchResults'));
+      assertEquals(null, handler.getArgs('getWallpaperSearchResults')[0][3]);
+    });
   });
 
   suite('Search', () => {
@@ -630,7 +660,15 @@
       createWallpaperSearchElement();
 
       wallpaperSearchCallbackRouterRemote.setHistory([
-        {image: '123', id: {high: BigInt(10), low: BigInt(1)}},
+        {
+          image: '123',
+          id: {high: BigInt(10), low: BigInt(1)},
+          descriptors: {
+            subject: 'foo',
+            mood: 'bar',
+            style: 'foobar',
+          },
+        },
         {image: '456', id: {high: BigInt(8), low: BigInt(2)}},
       ]);
       await wallpaperSearchCallbackRouterRemote.$.flushForTesting();
@@ -641,10 +679,12 @@
       (historyTile as HTMLElement).click();
 
       assertEquals(1, handler.getCallCount('setBackgroundToHistoryImage'));
-      assertEquals(
-          BigInt(10), handler.getArgs('setBackgroundToHistoryImage')[0].high);
-      assertEquals(
-          BigInt(1), handler.getArgs('setBackgroundToHistoryImage')[0].low);
+      const args = handler.getArgs('setBackgroundToHistoryImage');
+      assertEquals(BigInt(10), args[0][0].high);
+      assertEquals(BigInt(1), args[0][0].low);
+      assertEquals('foo', args[0][1].subject);
+      assertEquals('bar', args[0][1].mood);
+      assertEquals('foobar', args[0][1].style);
     });
 
     test('current history theme is checked', async () => {
@@ -687,6 +727,64 @@
           checkedResults[0]!.parentElement!.getAttribute('aria-current'),
           'true');
     });
+
+    test('labels history', async () => {
+      loadTimeData.overrideValues({
+        'wallpaperSearchHistoryResultLabelNoDescriptor': 'Image $1',
+        'wallpaperSearchHistoryResultLabel': 'Image $1 of $2',
+        'wallpaperSearchHistoryResultLabelB': 'Image $1 of $2, $3',
+        'wallpaperSearchHistoryResultLabelC': 'Image $1 of $2, $3',
+        'wallpaperSearchHistoryResultLabelBC': 'Image $1 of $2, $3, $4',
+      });
+      createWallpaperSearchElement();
+
+      wallpaperSearchCallbackRouterRemote.setHistory([
+        {image: '123', id: {high: BigInt(10), low: BigInt(1)}},
+        {
+          image: '456',
+          id: {high: BigInt(8), low: BigInt(2)},
+          descriptors: {
+            subject: 'foo',
+          },
+        },
+        {
+          image: '789',
+          id: {high: BigInt(8), low: BigInt(3)},
+          descriptors: {
+            subject: 'foo',
+            mood: 'bar',
+          },
+        },
+        {
+          image: '012',
+          id: {high: BigInt(8), low: BigInt(4)},
+          descriptors: {
+            subject: 'foo',
+            style: 'foobar',
+          },
+        },
+        {
+          image: '345',
+          id: {high: BigInt(10), low: BigInt(5)},
+          descriptors: {
+            subject: 'foo',
+            mood: 'bar',
+            style: 'foobar',
+          },
+        },
+      ]);
+      await wallpaperSearchCallbackRouterRemote.$.flushForTesting();
+
+      const historyTiles =
+          wallpaperSearchElement.$.historyCard.querySelectorAll('.tile.result');
+
+      assertEquals(historyTiles.length, 5);
+      assertEquals('Image 1', historyTiles[0]!.ariaLabel);
+      assertEquals('Image 2 of foo', historyTiles[1]!.ariaLabel);
+      assertEquals('Image 3 of foo, bar', historyTiles[2]!.ariaLabel);
+      assertEquals('Image 4 of foo, foobar', historyTiles[3]!.ariaLabel);
+      assertEquals('Image 5 of foo, foobar, bar', historyTiles[4]!.ariaLabel);
+    });
   });
 
   suite('Error', () => {
diff --git a/chrome/test/variations/test_utils/downloader.py b/chrome/test/variations/test_utils/downloader.py
index 464d514a..eec3426 100644
--- a/chrome/test/variations/test_utils/downloader.py
+++ b/chrome/test/variations/test_utils/downloader.py
@@ -111,22 +111,6 @@
   return chromedriver_path
 
 
-def download_chromedriver_linux_host(channel: str, version: str) -> str:
-  """Download the chromedriver that works with the given channel/version."""
-
-  # Find the same or next version of the given channel and version whose
-  # chromedriver is compatible with.
-  # Linux doesn't distribute canary, use dev instead.
-  if channel == 'canary':
-    channel = 'dev'
-  closest_version = find_closest_version(
-    release_os='linux', channel=channel, version=version)
-  downloaded_dir = _download_files_from_gcs(
-    str(closest_version), ['linux64/chromedriver_linux64.zip'])
-
-  return os.path.join(downloaded_dir, "chromedriver_linux64")
-
-
 def download_chrome_mac(version: str) -> str:
   files = ['mac-universal/chrome-mac.zip']
   downloaded_dir = _download_files_from_gcs(version, files)
diff --git a/chrome/updater/ipc/update_service_proxy_win.cc b/chrome/updater/ipc/update_service_proxy_win.cc
index a59bd31..7ba18e3 100644
--- a/chrome/updater/ipc/update_service_proxy_win.cc
+++ b/chrome/updater/ipc/update_service_proxy_win.cc
@@ -318,7 +318,7 @@
 
   static UpdateService::AppState IUpdaterAppStateToAppState(
       Microsoft::WRL::ComPtr<IUpdaterAppState> updater_app_state) {
-    DCHECK(updater_app_state);
+    CHECK(updater_app_state);
 
     UpdateService::AppState app_state;
     {
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc
index e007f61..e142dd69 100644
--- a/chrome/updater/test/integration_tests.cc
+++ b/chrome/updater/test/integration_tests.cc
@@ -547,16 +547,15 @@
     std::wstring pv;
     EXPECT_EQ(
         ERROR_SUCCESS,
-        base::win::RegKey(UpdaterScopeToHKeyRoot(UpdaterScope::kSystem),
+        base::win::RegKey(UpdaterScopeToHKeyRoot(GetTestScope()),
                           GetAppClientsKey(appid).c_str(), Wow6432(KEY_READ))
             .ReadValue(kRegValuePV, &pv));
     EXPECT_EQ(pv, base::ASCIIToWide(expected_version.GetString()));
 #else
-    const base::FilePath app_json_path =
-        GetInstallDirectory(UpdaterScope::kSystem)
-            ->DirName()
-            .AppendASCII(appid)
-            .AppendASCII("app.json");
+    const base::FilePath app_json_path = GetInstallDirectory(GetTestScope())
+                                             ->DirName()
+                                             .AppendASCII(appid)
+                                             .AppendASCII("app.json");
     JSONFileValueDeserializer parser(app_json_path,
                                      base::JSON_ALLOW_TRAILING_COMMAS);
     int error_code = 0;
@@ -581,8 +580,9 @@
       ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe_path));
       const base::CommandLine command = app.GetInstallCommandLine(install_v1);
       VLOG(2) << "Launch app setup command: " << command.GetCommandLineString();
-      const base::Process process =
-          base::LaunchProcess(MakeElevated(command), {});
+      const base::Process process = base::LaunchProcess(
+          IsSystemInstall(GetTestScope()) ? MakeElevated(command) : command,
+          {});
       if (!process.IsValid()) {
         VLOG(2) << "Failed to launch the app setup command.";
       }
@@ -591,10 +591,9 @@
                                                  &exit_code));
       EXPECT_EQ(0, exit_code);
 #if !BUILDFLAG(IS_WIN)
-      SetExistenceCheckerPath(app.appid,
-                              GetInstallDirectory(UpdaterScope::kSystem)
-                                  ->DirName()
-                                  .AppendASCII(app.appid));
+      SetExistenceCheckerPath(app.appid, GetInstallDirectory(GetTestScope())
+                                             ->DirName()
+                                             .AppendASCII(app.appid));
 #endif
     });
 
@@ -638,6 +637,29 @@
 
   scoped_refptr<IntegrationTestCommands> test_commands_;
 
+#if BUILDFLAG(IS_WIN)
+  static constexpr char kGlobalPolicyKey[] = "";
+  const TestApp kApp1 = {"test1", base::Version("1.0.0.0"),
+                         "Testapp2Setup.crx3", base::Version("2.0.0.0"),
+                         "Testapp2Setup.crx3"};
+  const TestApp kApp2 = {"test2", base::Version("100.0.0.0"),
+                         "Testapp2Setup.crx3", base::Version("101.0.0.0"),
+                         "Testapp2Setup.crx3"};
+  const TestApp kApp3 = {"test3", base::Version("1.0"), "Testapp2Setup.crx3",
+                         base::Version("1.1"), "Testapp2Setup.crx3"};
+#else
+  static constexpr char kGlobalPolicyKey[] = "global";
+  const TestApp kApp1 = {
+      "test1", base::Version("1.0.0.0"), "test_installer_test1_v1.crx3",
+      base::Version("2.0.0.0"), "test_installer_test1_v2.crx3"};
+  const TestApp kApp2 = {
+      "test2", base::Version("100.0.0.0"), "test_installer_test2_v1.crx3",
+      base::Version("101.0.0.0"), "test_installer_test2_v2.crx3"};
+  const TestApp kApp3 = {"test3", base::Version("1.0"),
+                         "test_installer_test3_v1.crx3", base::Version("1.1"),
+                         "test_installer_test3_v2.crx3"};
+#endif  // BUILDFLAG(IS_WIN)
+
  private:
   base::test::TaskEnvironment environment_;
   ScopedIPCSupportWrapper ipc_support_;
@@ -985,6 +1007,45 @@
   ASSERT_NO_FATAL_FAILURE(Uninstall());
 }
 
+TEST_F(IntegrationTest, UpdateErrorStatus) {
+  ScopedServer test_server(test_commands_);
+
+  ASSERT_NO_FATAL_FAILURE(Install());
+  ASSERT_NO_FATAL_FAILURE(ExpectInstalled());
+  ASSERT_NO_FATAL_FAILURE(InstallTestApp(kApp1, /*install_v1=*/true));
+
+  for (const char* app_response_status :
+       {"noupdate", "error-internal", "error-hash", "error-osnotsupported",
+        "error-hwnotsupported", "error-unsupportedprotocol"}) {
+    ExpectAppsUpdateSequence(
+        GetTestScope(), &test_server, {},
+        {
+            AppUpdateExpectation(
+                kApp1.GetInstallCommandLineArgs(/*install_v1=*/false),
+                kApp1.appid, kApp1.v1, kApp1.v2,
+                /*is_install=*/false,
+                /*should_update=*/false, false, "", "",
+                GetInstallerPath(kApp1.v2_crx),
+                /*always_serve_crx=*/false,
+                /*error_category=*/UpdateService::ErrorCategory::kNone,
+                /*error_code=*/0,
+                /*event_type=*/0,
+                /*custom_app_response=*/{}, app_response_status),
+        });
+    ASSERT_NO_FATAL_FAILURE(RunWake(0));
+    ASSERT_TRUE(WaitForUpdaterExit());
+    ASSERT_NO_FATAL_FAILURE(ExpectAppInstalled(kApp1.appid, kApp1.v1))
+        << "App is unexpectedly updated with update check status: "
+        << app_response_status;
+    ASSERT_NO_FATAL_FAILURE(SetLastChecked(base::Time::Now() - base::Hours(9)))
+        << "Failed to set last-checked to force next update check.";
+  }
+
+  ASSERT_NO_FATAL_FAILURE(ExpectUninstallPing(&test_server));
+  ASSERT_NO_FATAL_FAILURE(UninstallApp(kApp1.appid));
+  ASSERT_NO_FATAL_FAILURE(Uninstall());
+}
+
 TEST_F(IntegrationTest, UpdateApp) {
   ScopedServer test_server(test_commands_);
   ASSERT_NO_FATAL_FAILURE(Install());
@@ -1570,7 +1631,7 @@
 
 TEST_F(IntegrationTest, IdleServerExits) {
 #if BUILDFLAG(IS_WIN)
-  if (GetTestScope() == UpdaterScope::kSystem) {
+  if (IsSystemInstall(GetTestScope())) {
     GTEST_SKIP() << "System server startup is complicated on Windows.";
   }
 #endif
@@ -1925,25 +1986,8 @@
 
 #if BUILDFLAG(IS_WIN)
   static constexpr char kGlobalPolicyKey[] = "";
-  const TestApp kApp1 = {"test1", base::Version("1.0.0.0"),
-                         "Testapp2Setup.crx3", base::Version("2.0.0.0"),
-                         "Testapp2Setup.crx3"};
-  const TestApp kApp2 = {"test2", base::Version("100.0.0.0"),
-                         "Testapp2Setup.crx3", base::Version("101.0.0.0"),
-                         "Testapp2Setup.crx3"};
-  const TestApp kApp3 = {"test3", base::Version("1.0"), "Testapp2Setup.crx3",
-                         base::Version("1.1"), "Testapp2Setup.crx3"};
 #else
   static constexpr char kGlobalPolicyKey[] = "global";
-  const TestApp kApp1 = {
-      "test1", base::Version("1.0.0.0"), "test_installer_test1_v1.crx3",
-      base::Version("2.0.0.0"), "test_installer_test1_v2.crx3"};
-  const TestApp kApp2 = {
-      "test2", base::Version("100.0.0.0"), "test_installer_test2_v1.crx3",
-      base::Version("101.0.0.0"), "test_installer_test2_v2.crx3"};
-  const TestApp kApp3 = {"test3", base::Version("1.0"),
-                         "test_installer_test3_v1.crx3", base::Version("1.1"),
-                         "test_installer_test3_v2.crx3"};
 #endif  // BUILDFLAG(IS_WIN)
 };
 
diff --git a/chrome/updater/test/integration_tests_impl.cc b/chrome/updater/test/integration_tests_impl.cc
index f5b0799..24ed48c 100644
--- a/chrome/updater/test/integration_tests_impl.cc
+++ b/chrome/updater/test/integration_tests_impl.cc
@@ -119,11 +119,12 @@
     const base::FilePath& update_file,
     const std::string& run_action,
     const std::string& arguments,
-    const std::optional<std::string>& file_hash = std::nullopt) {
+    const std::optional<std::string>& file_hash = std::nullopt,
+    const std::optional<std::string>& status = std::nullopt) {
   return base::StringPrintf(
       R"(    {)"
       R"(      "appid":"%s",)"
-      R"(      "status":"ok",)"
+      R"(      "status":"%s",)"
       R"(%s)"
       R"(      "updatecheck":{)"
       R"(        "status":"ok",)"
@@ -140,7 +141,7 @@
       R"(        })"
       R"(      })"
       R"(    })",
-      base::ToLowerASCII(app_id).c_str(),
+      base::ToLowerASCII(app_id).c_str(), status ? status->c_str() : "ok",
       !install_data_index.empty()
           ? base::StringPrintf(
                 R"(     "data":[{ "status":"ok", "name":"install", )"
@@ -337,7 +338,8 @@
     const UpdateService::ErrorCategory error_category,
     const int error_code,
     const int event_type,
-    const std::string& custom_app_response)
+    const std::string& custom_app_response,
+    const std::string& response_status)
     : args(args),
       app_id(app_id),
       from_version(from_version),
@@ -352,7 +354,8 @@
       error_category(error_category),
       error_code(error_code),
       event_type(event_type),
-      custom_app_response(custom_app_response) {}
+      custom_app_response(custom_app_response),
+      response_status(response_status.empty() ? "ok" : response_status) {}
 AppUpdateExpectation::AppUpdateExpectation(const AppUpdateExpectation&) =
     default;
 AppUpdateExpectation::~AppUpdateExpectation() = default;
@@ -570,7 +573,8 @@
                                       : base_name;
     app_responses.push_back(GetUpdateResponseForApp(
         app.app_id, "", test_server->update_url().spec(), app.to_version,
-        crx_path, run_action.MaybeAsASCII().c_str(), app.args));
+        crx_path, run_action.MaybeAsASCII().c_str(), app.args, std::nullopt,
+        app.response_status));
   }
   test_server->ExpectOnce({request::GetPathMatcher(test_server->update_path()),
                            request::GetUpdaterUserAgentMatcher(),
@@ -606,7 +610,7 @@
                app.from_version.GetString().c_str(),
                app.to_version.GetString().c_str())})},
           ")]}'\n");
-    } else if (app.custom_app_response.empty()) {
+    } else if (app.custom_app_response.empty() && app.response_status == "ok") {
       // Event ping for apps that doesn't update.
       test_server->ExpectOnce(
           {request::GetPathMatcher(test_server->update_path()),
diff --git a/chrome/updater/test/integration_tests_impl.h b/chrome/updater/test/integration_tests_impl.h
index d2a90ff..c8a33609 100644
--- a/chrome/updater/test/integration_tests_impl.h
+++ b/chrome/updater/test/integration_tests_impl.h
@@ -58,7 +58,8 @@
                        const int error_code = static_cast<int>(
                            UpdateService::Result::kUpdateCanceled),
                        const int event_type = /*EVENT_UPDATE_COMPLETE=*/3,
-                       const std::string& custom_app_response = {});
+                       const std::string& custom_app_response = {},
+                       const std::string& response_status = {});
   AppUpdateExpectation(const AppUpdateExpectation&);
   ~AppUpdateExpectation();
 
@@ -77,6 +78,7 @@
   const int error_code;
   const int event_type;
   const std::string custom_app_response;
+  const std::string response_status;
 };
 
 // Returns the path to the updater installer program (in the build output
diff --git a/chrome/updater/test/test_installer/test_app_setup.sh b/chrome/updater/test/test_installer/test_app_setup.sh
index bf39549..3d15404 100755
--- a/chrome/updater/test/test_installer/test_app_setup.sh
+++ b/chrome/updater/test/test_installer/test_app_setup.sh
@@ -7,6 +7,7 @@
 # path.
 
 declare appid=MockApp
+declare system=0
 declare company="Chromium"
 declare product_version="1.0.0.0"
 declare install=1
@@ -15,6 +16,9 @@
     --appid=*)
      appid="${i#*=}"
      ;;
+    --system)
+     system=1
+     ;;
     --company=*)
      company="${i#*=}"
      ;;
@@ -31,9 +35,19 @@
 
 declare -r install_file="app.json"
 if [[ "${OSTYPE}" =~ ^"darwin" ]]; then
-  declare -r install_path="/Library/Application Support/${company}/${appid}"
+  if (( "${system}" == 1 )); then
+    declare -r install_path="/Library/Application Support/${company}/${appid}"
+  else
+    declare -r \
+        install_path="${HOME}/Library/Application Support/${company}/${appid}"
+  fi
 else
-  declare -r install_path="/opt/${company}/${appid}"
+  declare install_path="/opt/${company}/${appid}"
+  if (( "${system}" == 1 )); then
+    declare -r install_path="/opt/${company}/${appid}"
+  else
+    declare -r install_path="${HOME}/.local/${company}/${appid}"
+  fi
 fi
 
 if (( "${install}" == 1 )); then
@@ -50,4 +64,4 @@
 else
   rm -rf "${install_path}" 2> /dev/null
   echo "Uninstall ${appid} at: ${install_path}."
-fi
\ No newline at end of file
+fi
diff --git a/chromeos/crosapi/mojom/feedback.mojom b/chromeos/crosapi/mojom/feedback.mojom
index 25e9541..2e003db3 100644
--- a/chromeos/crosapi/mojom/feedback.mojom
+++ b/chromeos/crosapi/mojom/feedback.mojom
@@ -11,7 +11,7 @@
 // Note: When you add a new value, please add a test case accordingly in:
 // chrome/browser/feedback/show_feedback_page_lacros_browertest.cc.
 //
-// Next MinVersion: 5
+// Next MinVersion: 6
 // Next ID: 9
 //
 [Stable, Extensible]
@@ -29,6 +29,7 @@
   [MinVersion=5] kFeedbackSourceSettingsPerformancePage = 9,
   [MinVersion=5] kFeedbackSourceProfileErrorDialog = 10,
   [MinVersion=5] kFeedbackSourceQuickOffice=11,
+  [MinVersion=6] kFeedbackSourceAI=12,
 };
 
 [Stable]
@@ -54,6 +55,8 @@
 
   // Autofill metadata (e.g. form signatures, last autofill event type, etc).
  [MinVersion=1] mojo_base.mojom.Value? autofill_metadata@6;
+
+ [MinVersion=2] mojo_base.mojom.Value? ai_metadata@7;
 };
 
 // This interface is implemented by ash-chrome. It allows lacros-chrome to
diff --git a/clank b/clank
index 542d4f7..fd941d8 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 542d4f710ed7446ff82fa80babd76cf24a5a83bd
+Subproject commit fd941d8a913faebbdaddb132368e9eb723e0634d
diff --git a/components/BUILD.gn b/components/BUILD.gn
index dbe1749e..a3400030 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -280,6 +280,7 @@
     "//components/prefs:unit_tests",
     "//components/profile_metrics:unit_tests",
     "//components/proxy_config:unit_tests",
+    "//components/push_notification:unit_tests",
     "//components/qr_code_generator:unit_tests",
     "//components/query_parser:unit_tests",
     "//components/reading_list/core:unit_tests",
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/chips/ChipsCoordinator.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/chips/ChipsCoordinator.java
index 62793d7c..1868f2d 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/chips/ChipsCoordinator.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/chips/ChipsCoordinator.java
@@ -131,9 +131,15 @@
             int position = parent.getChildAdapterPosition(view);
             boolean isFirst = position == 0;
             boolean isLast = position == parent.getAdapter().getItemCount() - 1;
+            // using 'parent' not 'view' since 'view' did not layout yet, so view's
+            // #getLayoutDirection() won't return correct value.
+            boolean isRtl = (parent.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
 
-            outRect.left = isFirst ? mSidePaddingPx : mChipSpacingPx;
-            outRect.right = isLast ? mSidePaddingPx : mChipSpacingPx;
+            @Px int startPadding = isFirst ? mSidePaddingPx : mChipSpacingPx;
+            @Px int endPadding = isLast ? mSidePaddingPx : mChipSpacingPx;
+
+            outRect.left = isRtl ? endPadding : startPadding;
+            outRect.right = isRtl ? startPadding : endPadding;
         }
     }
 }
diff --git a/components/compose_strings.grdp b/components/compose_strings.grdp
index 0b3fa41..60543697 100644
--- a/components/compose_strings.grdp
+++ b/components/compose_strings.grdp
@@ -153,6 +153,10 @@
     <ph name="BEGIN_SURVEY_LINK">&lt;a href="#" id="surveyLink" role="button"&gt;</ph>survey<ph name="END_SURVEY_LINK">&lt;/a&gt;</ph>
   </message>
 
+  <message name="IDS_COMPOSE_FOOTER_FISHFOOD_ON_DEVICE_USED" desc="A footer indicating that on-device evaluation was used." translateable="false">
+    On-device evaluation used.
+  </message>
+
   <!-- Miscellaneous -->
   <message name="IDS_COMPOSE_FEEDBACK_PLACEHOLDER" desc="Placeholder text for feedback text field" translateable="false">
     Send feedback for Help me write.
diff --git a/components/content_settings/core/browser/content_settings_default_provider.cc b/components/content_settings/core/browser/content_settings_default_provider.cc
index b7b105e7..3da22d7 100644
--- a/components/content_settings/core/browser/content_settings_default_provider.cc
+++ b/components/content_settings/core/browser/content_settings_default_provider.cc
@@ -231,6 +231,33 @@
   return std::make_unique<DefaultRuleIterator>(it->second.Clone());
 }
 
+std::unique_ptr<OwnedRule> DefaultProvider::GetRule(
+    const GURL& primary_url,
+    const GURL& secondary_url,
+    ContentSettingsType content_type,
+    bool off_the_record,
+    const PartitionKey& partition_key) const {
+  // The default provider never has off-the-record-specific settings.
+  if (off_the_record) {
+    return nullptr;
+  }
+
+  base::AutoLock lock(lock_);
+  const auto it = default_settings_.find(content_type);
+  if (it == default_settings_.end()) {
+    NOTREACHED();
+    return nullptr;
+  }
+
+  if (it->second.is_none()) {
+    return nullptr;
+  }
+
+  return std::make_unique<OwnedRule>(ContentSettingsPattern::Wildcard(),
+                                     ContentSettingsPattern::Wildcard(),
+                                     it->second.Clone(), RuleMetaData{});
+}
+
 void DefaultProvider::ClearAllContentSettingsRules(
     ContentSettingsType content_type,
     const PartitionKey& partition_key) {
diff --git a/components/content_settings/core/browser/content_settings_default_provider.h b/components/content_settings/core/browser/content_settings_default_provider.h
index d660599..ef77206 100644
--- a/components/content_settings/core/browser/content_settings_default_provider.h
+++ b/components/content_settings/core/browser/content_settings_default_provider.h
@@ -44,6 +44,13 @@
       bool off_the_record,
       const PartitionKey& partition_key) const override;
 
+  std::unique_ptr<OwnedRule> GetRule(
+      const GURL& primary_url,
+      const GURL& secondary_url,
+      ContentSettingsType content_type,
+      bool off_the_record,
+      const PartitionKey& partition_key) const override;
+
   bool SetWebsiteSetting(const ContentSettingsPattern& primary_pattern,
                          const ContentSettingsPattern& secondary_pattern,
                          ContentSettingsType content_type,
diff --git a/components/content_settings/core/common/features.cc b/components/content_settings/core/common/features.cc
index 6455329..55304f9c 100644
--- a/components/content_settings/core/common/features.cc
+++ b/components/content_settings/core/common/features.cc
@@ -74,6 +74,10 @@
              "ImprovedSemanticsActivityIndicators",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kLeftHandSideActivityIndicators,
+             "LeftHandSideActivityIndicators",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 BASE_FEATURE(kTrackingProtection3pcd,
              "TrackingProtection3pcd",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/components/content_settings/core/common/features.h b/components/content_settings/core/common/features.h
index bcb8ed3..80575c7 100644
--- a/components/content_settings/core/common/features.h
+++ b/components/content_settings/core/common/features.h
@@ -97,6 +97,10 @@
 COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES)
 BASE_DECLARE_FEATURE(kImprovedSemanticsActivityIndicators);
 
+// Move activity indicators to the left-hand side of Omnibox.
+COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES)
+BASE_DECLARE_FEATURE(kLeftHandSideActivityIndicators);
+
 // Feature to enable redesigned tracking protection UX + prefs for 3PCD.
 COMPONENT_EXPORT(CONTENT_SETTINGS_FEATURES)
 BASE_DECLARE_FEATURE(kTrackingProtection3pcd);
diff --git a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
index 96859a86..e99bd30c 100644
--- a/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
+++ b/components/external_intents/android/java/src/org/chromium/components/external_intents/ExternalNavigationHandler.java
@@ -144,42 +144,6 @@
         "com.google.android.instantapps.nmr1.VIEW"
     };
 
-    /**
-     * Histogram for the result of an intent scheme navigation.
-     * This enum is used in UMA, do not reorder values.
-     */
-    @IntDef({
-        IntentUriNavigationResult.WITH_FALLBACK_LAUNCHED_INTENT,
-        IntentUriNavigationResult.WITH_FALLBACK_USED_FALLBACK,
-        IntentUriNavigationResult.WITH_FALLBACK_NO_OVERRIDE,
-        IntentUriNavigationResult.WITH_FALLBACK_ASYNC_RESULT,
-        IntentUriNavigationResult.NO_FALLBACK_LAUNCHED_INTENT,
-        IntentUriNavigationResult.NO_FALLBACK_NO_OVERRIDE,
-        IntentUriNavigationResult.NO_FALLBACK_ASYNC_RESULT,
-        IntentUriNavigationResult.NUM_ENTRIES
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IntentUriNavigationResult {
-        /* Intent with an unused fallback URL was launched. */
-        int WITH_FALLBACK_LAUNCHED_INTENT = 0;
-        /* Intent fallback URL was used. */
-        int WITH_FALLBACK_USED_FALLBACK = 1;
-        /* Intent with an unused fallback URL was blocked. */
-        int WITH_FALLBACK_NO_OVERRIDE = 2;
-        /* Intent with a fallback URL prompted the user. */
-        int WITH_FALLBACK_ASYNC_RESULT = 3;
-        /* Intent without a fallback URL was launched. */
-        int NO_FALLBACK_LAUNCHED_INTENT = 4;
-        /* Intent without a fallback URL was blocked. */
-        int NO_FALLBACK_NO_OVERRIDE = 5;
-        /* Intent without a fallback URL prompted the user. */
-        int NO_FALLBACK_ASYNC_RESULT = 6;
-
-        int NUM_ENTRIES = 7;
-    }
-
-    private static final String INTENT_URI_RESULT_NAME = "Android.Intent.IntentUriNavigationResult";
-
     // Helper class to return a boolean by reference.
     private static class MutableBoolean {
         private Boolean mValue;
@@ -642,7 +606,6 @@
                             canLaunchExternalFallbackResult.get());
         }
         if (debug()) printDebugShouldOverrideUrlLoadingResultType(result);
-        if (isIntentUrl) captureIntentSchemeMetrics(result, browserFallbackUrl);
 
         if (result.getResultType() == OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION) {
             params.onAsyncActionStarted();
@@ -736,57 +699,6 @@
         Log.i(TAG, "shouldOverrideUrlLoading result: " + resultString);
     }
 
-    private void captureIntentSchemeMetrics(
-            OverrideUrlLoadingResult result, GURL browserFallbackUrl) {
-        @IntentUriNavigationResult int value = IntentUriNavigationResult.NO_FALLBACK_NO_OVERRIDE;
-        if (browserFallbackUrl.isEmpty()) {
-            switch (result.getResultType()) {
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT:
-                    value = IntentUriNavigationResult.NO_FALLBACK_LAUNCHED_INTENT;
-                    break;
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION:
-                    value = IntentUriNavigationResult.NO_FALLBACK_ASYNC_RESULT;
-                    break;
-                case OverrideUrlLoadingResultType.NO_OVERRIDE:
-                    value = IntentUriNavigationResult.NO_FALLBACK_NO_OVERRIDE;
-                    break;
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB:
-                    // Quirk of incognito intent scheme URLs synchronously clobbering the tab with
-                    // the target URL when the dialog can't be shown.
-                    value = IntentUriNavigationResult.NO_FALLBACK_NO_OVERRIDE;
-                    break;
-                default:
-                    assert false;
-                    break;
-            }
-        } else {
-            switch (result.getResultType()) {
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_EXTERNAL_INTENT:
-                    if (result.wasExternalFallbackUrlLaunch()) {
-                        value = IntentUriNavigationResult.WITH_FALLBACK_USED_FALLBACK;
-                    } else {
-                        value = IntentUriNavigationResult.WITH_FALLBACK_LAUNCHED_INTENT;
-                    }
-                    break;
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_ASYNC_ACTION:
-                    value = IntentUriNavigationResult.WITH_FALLBACK_ASYNC_RESULT;
-                    break;
-                case OverrideUrlLoadingResultType.NO_OVERRIDE:
-                    value = IntentUriNavigationResult.WITH_FALLBACK_NO_OVERRIDE;
-                    break;
-                case OverrideUrlLoadingResultType.OVERRIDE_WITH_NAVIGATE_TAB:
-                    value = IntentUriNavigationResult.WITH_FALLBACK_USED_FALLBACK;
-                    break;
-                default:
-                    assert false;
-                    break;
-            }
-        }
-
-        RecordHistogram.recordEnumeratedHistogram(
-                INTENT_URI_RESULT_NAME, value, IntentUriNavigationResult.NUM_ENTRIES);
-    }
-
     private boolean resolversSubsetOf(List<ResolveInfo> infos, List<ResolveInfo> container) {
         if (container == null) return false;
         HashSet<ComponentName> containerSet = new HashSet<>();
diff --git a/components/eye_dropper/eye_dropper_view.cc b/components/eye_dropper/eye_dropper_view.cc
index 12107fb..c537058f 100644
--- a/components/eye_dropper/eye_dropper_view.cc
+++ b/components/eye_dropper/eye_dropper_view.cc
@@ -272,7 +272,7 @@
   const gfx::Size padding((size().width() - diameter) / 2,
                           (size().height() - diameter) / 2);
 
-  if (GetWidget()->IsTranslucentWindowOpacitySupported()) {
+  if (views::Widget::IsWindowCompositingSupported()) {
     // Clip circle for magnified projection only when the widget
     // supports translucency.
     SkPath clip_path;
@@ -355,7 +355,7 @@
   flags.setStrokeWidth(2);
   flags.setColor(color_provider->GetColor(color::kColorEyedropperBoundary));
   flags.setAntiAlias(true);
-  if (GetWidget()->IsTranslucentWindowOpacitySupported()) {
+  if (views::Widget::IsWindowCompositingSupported()) {
     view_canvas->DrawCircle(
         gfx::PointF(size().width() / 2, size().height() / 2), diameter / 2,
         flags);
diff --git a/components/feature_engagement/public/feature_configurations.cc b/components/feature_engagement/public/feature_configurations.cc
index f9641942..142bbb09 100644
--- a/components/feature_engagement/public/feature_configurations.cc
+++ b/components/feature_engagement/public/feature_configurations.cc
@@ -548,7 +548,7 @@
 
   if (kIPHComposeMenuNewBadgeFeature.name == feature->name) {
     // A config that allows the new badge attached to the Compose feature
-    // entrypoint in the right-click menu to be shown at most 4 times in a
+    // entrypoint in the right-click menu to be shown at most 10 times in a
     // 10-day window and only while the user has opened the Compose feature less
     // than 3 times.
     absl::optional<FeatureConfig> config = FeatureConfig();
@@ -556,7 +556,7 @@
     config->availability = Comparator(ANY, 0);
     config->session_rate = Comparator(ANY, 0);
     config->trigger = EventConfig("compose_menu_new_badge_triggered",
-                                  Comparator(LESS_THAN, 4), 10, 360);
+                                  Comparator(LESS_THAN, 10), 10, 360);
     config->used = EventConfig("compose_menu_item_activated",
                                Comparator(LESS_THAN, 3), 360, 360);
     return config;
diff --git a/components/feature_engagement/public/feature_constants.cc b/components/feature_engagement/public/feature_constants.cc
index 050fe11..452da522 100644
--- a/components/feature_engagement/public/feature_constants.cc
+++ b/components/feature_engagement/public/feature_constants.cc
@@ -57,7 +57,7 @@
              base::FEATURE_ENABLED_BY_DEFAULT);
 BASE_FEATURE(kIPHExperimentalAIPromoFeature,
              "IPH_ExperimentalAIPromo",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 BASE_FEATURE(kIPHExtensionsMenuFeature,
              "IPH_ExtensionsMenu",
diff --git a/components/feedback/redaction_tool/inprocess_metrics_recorder.cc b/components/feedback/redaction_tool/inprocess_metrics_recorder.cc
index b5cacbb..9fdcb75 100644
--- a/components/feedback/redaction_tool/inprocess_metrics_recorder.cc
+++ b/components/feedback/redaction_tool/inprocess_metrics_recorder.cc
@@ -27,6 +27,11 @@
   UMA_HISTOGRAM_ENUMERATION(kCreditCardRedactionHistogram, step);
 }
 
+void InprocessMetricsRecorder::RecordRedactionToolCallerHistogram(
+    RedactionToolCaller step) {
+  UMA_HISTOGRAM_ENUMERATION(kRedactionToolCallerHistogram, step);
+}
+
 void InprocessMetricsRecorder::RecordTimeSpentRedactingHistogram(
     base::TimeDelta time_spent) {
   UMA_HISTOGRAM_MEDIUM_TIMES(kTimeSpentRedactingHistogram, time_spent);
diff --git a/components/feedback/redaction_tool/inprocess_metrics_recorder.h b/components/feedback/redaction_tool/inprocess_metrics_recorder.h
index 2106bce..861a2f9 100644
--- a/components/feedback/redaction_tool/inprocess_metrics_recorder.h
+++ b/components/feedback/redaction_tool/inprocess_metrics_recorder.h
@@ -22,6 +22,7 @@
   // redaction::RedactionToolMetricsRecorder:
   void RecordPIIRedactedHistogram(PIIType pii_type) override;
   void RecordCreditCardRedactionHistogram(CreditCardDetection step) override;
+  void RecordRedactionToolCallerHistogram(RedactionToolCaller step) override;
   void RecordTimeSpentRedactingHistogram(base::TimeDelta time_spent) override;
 };
 
diff --git a/components/feedback/redaction_tool/redaction_tool.cc b/components/feedback/redaction_tool/redaction_tool.cc
index a021e83..da14cd9 100644
--- a/components/feedback/redaction_tool/redaction_tool.cc
+++ b/components/feedback/redaction_tool/redaction_tool.cc
@@ -610,17 +610,21 @@
   return detected;
 }
 
-std::string RedactionTool::Redact(const std::string& input) {
+std::string RedactionTool::Redact(const std::string& input,
+                                  const base::Location& location) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return RedactAndKeepSelected(input, /*pii_types_to_keep=*/{});
+  return RedactAndKeepSelected(input, /*pii_types_to_keep=*/{}, location);
 }
 
 std::string RedactionTool::RedactAndKeepSelected(
     const std::string& input,
-    const std::set<PIIType>& pii_types_to_keep) {
+    const std::set<PIIType>& pii_types_to_keep,
+    const base::Location& location) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   base::AssertLongCPUWorkAllowed();
 
+  RedactionToolCaller caller = GetCaller(location);
+  metrics_recorder_->RecordRedactionToolCallerHistogram(caller);
   const base::TimeTicks redaction_start = base::TimeTicks::Now();
 
   // Copy |input| so we can modify it.
@@ -1094,6 +1098,33 @@
   }
 }
 
+RedactionToolCaller RedactionTool::GetCaller(const base::Location& location) {
+  std::string filePath = location.file_name();
+  if (filePath.empty() || filePath.c_str() == nullptr) {
+    return RedactionToolCaller::kUndetermined;
+  }
+
+  std::string fileName = filePath.substr(filePath.find_last_of("/\\") + 1);
+
+  if (fileName == "redaction_tool_unittest.cc") {
+    return RedactionToolCaller::kUnitTest;
+  } else if (fileName == "system_log_uploader.cc") {
+    return RedactionToolCaller::kSysLogUploader;
+  } else if (fileName == "system_logs_fetcher.cc") {
+    return RedactionToolCaller::kSysLogFetcher;
+  } else if (fileName == "log_source_access_manager.cc") {
+    return RedactionToolCaller::kBrowserSystemLogs;
+  } else if (fileName == "feedback_common.cc") {
+    return RedactionToolCaller::kFeedbackTool;
+  } else if (filePath.find("support_tool") != std::string::npos) {
+    return RedactionToolCaller::kSupportTool;
+  } else if (filePath.find("error_reporting") != std::string::npos) {
+    return RedactionToolCaller::kErrorReporting;
+  } else {
+    return RedactionToolCaller::kUnknown;
+  }
+}
+
 std::string RedactionTool::RedactCustomPatternWithContext(
     const std::string& input,
     const CustomPatternWithAlias& pattern,
diff --git a/components/feedback/redaction_tool/redaction_tool.h b/components/feedback/redaction_tool/redaction_tool.h
index 5e59b51..1702af7 100644
--- a/components/feedback/redaction_tool/redaction_tool.h
+++ b/components/feedback/redaction_tool/redaction_tool.h
@@ -12,6 +12,7 @@
 
 #include "base/component_export.h"
 #include "base/feature_list.h"
+#include "base/location.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/sequence_checker.h"
@@ -72,7 +73,11 @@
   // addresses) in |input| is replaced with unique identifiers.
   // This is an expensive operation. Make sure not to execute this on the UI
   // thread.
-  std::string Redact(const std::string& input);
+  // The |location| is automatically determined to be the caller of the function
+  // and is used for metrics. It's not passed by the caller.
+  std::string Redact(
+      const std::string& input,
+      const base::Location& location = base::Location::Current());
 
   // Attempts to redact PII sensitive data from |input| except the data that
   // fits in one of the PII types in |pii_types_to_keep| and returns the
@@ -81,8 +86,12 @@
   // Android storage paths will be partially redacted (only hashes) if
   // |pii_types_to_keep| contains PIIType::kURL or
   // PIIType::kAndroidAppStoragePath and not PIIType::kHash.
-  std::string RedactAndKeepSelected(const std::string& input,
-                                    const std::set<PIIType>& pii_types_to_keep);
+  // The |location| is automatically determined to be the caller of the function
+  // and is used for metrics.
+  std::string RedactAndKeepSelected(
+      const std::string& input,
+      const std::set<PIIType>& pii_types_to_keep,
+      const base::Location& location = base::Location::Current());
 
   // Setting `enabled` to `true` redacts credit card numbers in addition to
   // gathering UMA metrics. If not called or `enabled` set to `false` credit
@@ -135,6 +144,9 @@
       std::string input,
       const std::set<PIIType>& pii_types_to_keep);
 
+  // Gets the caller of the Redaction tool by looking at the |location|.
+  RedactionToolCaller GetCaller(const base::Location& location);
+
   // Detects PII sensitive data in |input| using custom patterns. Adds the
   // detected PII sensitive data to corresponding PII type key in |detected|.
   void DetectWithCustomPatterns(
diff --git a/components/feedback/redaction_tool/redaction_tool_metrics_recorder.h b/components/feedback/redaction_tool/redaction_tool_metrics_recorder.h
index be314ae2..3845a370 100644
--- a/components/feedback/redaction_tool/redaction_tool_metrics_recorder.h
+++ b/components/feedback/redaction_tool/redaction_tool_metrics_recorder.h
@@ -25,9 +25,27 @@
   kMaxValue = kValidated,
 };
 
+// These values are logged to UMA. Entries should not be renumbered and
+// numeric values should never be reused. Please keep in sync with
+// "RedactionToolCaller" in //tools/metrics/histograms/enums.xml.
+enum class RedactionToolCaller {
+  kSysLogUploader = 1,
+  kSysLogFetcher = 2,
+  kSupportTool = 3,
+  kErrorReporting = 4,
+  kFeedbackTool = 5,
+  kBrowserSystemLogs = 6,
+  kUnitTest = 7,
+  kUndetermined = 8,
+  kUnknown = 9,
+  kMaxValue = kUnknown,
+};
+
 inline constexpr char kPIIRedactedHistogram[] = "Feedback.RedactionTool";
 inline constexpr char kCreditCardRedactionHistogram[] =
     "Feedback.RedactionTool.CreditCardMatch";
+inline constexpr char kRedactionToolCallerHistogram[] =
+    "Feedback.RedactionTool.Caller";
 
 // This class is the platform independent interface to record histograms using
 // the platform specific libraries.
@@ -50,6 +68,9 @@
   // against credit card checks.
   virtual void RecordCreditCardRedactionHistogram(CreditCardDetection step) = 0;
 
+  // Records the caller who initiated the call to the Redaction Tool.
+  virtual void RecordRedactionToolCallerHistogram(RedactionToolCaller step) = 0;
+
   // Records the `time_spent` in milliseconds for redacting an input text.
   virtual void RecordTimeSpentRedactingHistogram(
       base::TimeDelta time_spent) = 0;
diff --git a/components/feedback/redaction_tool/redaction_tool_unittest.cc b/components/feedback/redaction_tool/redaction_tool_unittest.cc
index 32afa73..dbbd7a4 100644
--- a/components/feedback/redaction_tool/redaction_tool_unittest.cc
+++ b/components/feedback/redaction_tool/redaction_tool_unittest.cc
@@ -683,6 +683,25 @@
   redactor_.EnableCreditCardRedaction(true);
   std::string redaction_input;
   std::string redaction_output;
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUnitTest, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSysLogUploader, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSysLogFetcher, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSupportTool, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kErrorReporting, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kFeedbackTool, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kBrowserSystemLogs, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUndetermined, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUnknown, 0);
+
   using enum CreditCardDetection;
   ExpectBucketCount(kCreditCardRedactionHistogram, kRegexMatch, 0);
   ExpectBucketCount(kCreditCardRedactionHistogram, kTimestamp, 0);
@@ -723,6 +742,24 @@
   // This isn't handled by the redaction tool but rather in Shill. It's part of
   // the enum for historical reasons.
   ExpectBucketCount(kPIIRedactedHistogram, PIIType::kEAP, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUnitTest, 1);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSysLogUploader, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSysLogFetcher, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kSupportTool, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kErrorReporting, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kFeedbackTool, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kBrowserSystemLogs, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUndetermined, 0);
+  ExpectBucketCount(kRedactionToolCallerHistogram,
+                    RedactionToolCaller::kUnknown, 0);
 
   ExpectBucketCount(kCreditCardRedactionHistogram, kRegexMatch, 16);
   ExpectBucketCount(kCreditCardRedactionHistogram, kTimestamp, 2);
diff --git a/components/history/core/browser/features.cc b/components/history/core/browser/features.cc
index 77990c1..e61f66c 100644
--- a/components/history/core/browser/features.cc
+++ b/components/history/core/browser/features.cc
@@ -84,6 +84,13 @@
              "SyncSegmentsData",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// When enabled, prefer to use the new recovery module to recover the
+// `TopSitesDatabase` database. See https://crbug.com/1385500 for details.
+// This is a kill switch and is not intended to be used in a field trial.
+BASE_FEATURE(kTopSitesDatabaseUseBuiltInRecoveryIfSupported,
+             "TopSitesDatabaseUseBuiltInRecoveryIfSupported",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // The maximum number of New Tab Page displays to show with synced segments
 // data.
 const base::FeatureParam<int> kMaxNumNewTabPageDisplays(
diff --git a/components/history/core/browser/features.h b/components/history/core/browser/features.h
index 08c5ac1..7b3b151 100644
--- a/components/history/core/browser/features.h
+++ b/components/history/core/browser/features.h
@@ -29,6 +29,9 @@
 // is enabled; do not check `kSyncSegmentsData` directly.
 BASE_DECLARE_FEATURE(kSyncSegmentsData);
 
+// Kill switch for use of the new SQL recovery module in `TopSitesDatabase`.
+BASE_DECLARE_FEATURE(kTopSitesDatabaseUseBuiltInRecoveryIfSupported);
+
 // Returns true when both full history sync and synced segments data are
 // enabled.
 bool IsSyncSegmentsDataEnabled();
diff --git a/components/history/core/browser/top_sites_database.cc b/components/history/core/browser/top_sites_database.cc
index 5ab50d3..433e8f4 100644
--- a/components/history/core/browser/top_sites_database.cc
+++ b/components/history/core/browser/top_sites_database.cc
@@ -12,10 +12,13 @@
 
 #include "base/check.h"
 #include "base/check_op.h"
+#include "base/debug/dump_without_crashing.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/sequence_checker.h"
+#include "components/history/core/browser/features.h"
 #include "components/history/core/browser/history_types.h"
 #include "components/history/core/browser/top_sites.h"
 #include "sql/database.h"
@@ -72,7 +75,7 @@
 // depending on the operation broken.  This table has large rows, which will use
 // overflow pages, so it is possible (though unlikely) that a chain could fit
 // together and yield a row with errors.
-void FixTopSitesTable(sql::Database* db, int version) {
+void FixTopSitesTable(sql::Database& db) {
   // Enforce invariant that url_rank>=0 forms a contiguous series.
   // TODO(shess): I have not found an UPDATE+SUBSELECT method of managing this.
   // It can be done with a temporary table and a subselect, but doing it
@@ -82,11 +85,11 @@
       "SELECT url_rank,rowid FROM top_sites "
       "WHERE url_rank<>-1 "
       "ORDER BY url_rank";
-  sql::Statement select_statement(db->GetUniqueStatement(kByRankSql));
+  sql::Statement select_statement(db.GetUniqueStatement(kByRankSql));
 
   static constexpr char kAdjustRankSql[] =
       "UPDATE top_sites SET url_rank=? WHERE rowid=?";
-  sql::Statement update_statement(db->GetUniqueStatement(kAdjustRankSql));
+  sql::Statement update_statement(db.GetUniqueStatement(kAdjustRankSql));
 
   // Update any rows where `next_rank` doesn't match `url_rank`.
   int next_rank = 0;
@@ -106,7 +109,7 @@
 // constraints.
 void RecoverAndFixup(sql::Database* db, const base::FilePath& db_path) {
   // NOTE(shess): If the version changes, review this code.
-  DCHECK_EQ(5, kVersionNumber);
+  static_assert(kVersionNumber == 5);
 
   std::unique_ptr<sql::Recovery> recovery =
       sql::Recovery::BeginRecoverDatabase(db, db_path);
@@ -140,8 +143,7 @@
     return;
   }
 
-  // TODO(shess): Inline this?
-  FixTopSitesTable(recovery->db(), version);
+  FixTopSitesTable(*recovery->db());
 
   std::ignore = sql::Recovery::Recovered(std::move(recovery));
 }
@@ -154,7 +156,45 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(db_);
 
-  // Attempt to recover corrupt databases.
+  // TODO(https://crbug.com/1513464): Simplify this code as soon as we're
+  // confident that we can remove sql::Recovery.
+  if (base::FeatureList::IsEnabled(
+          kTopSitesDatabaseUseBuiltInRecoveryIfSupported) &&
+      sql::BuiltInRecovery::ShouldAttemptRecovery(db_.get(), extended_error)) {
+    bool recovery_was_attempted = sql::BuiltInRecovery::RecoverIfPossible(
+        db_.get(), extended_error,
+        sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
+        &kTopSitesDatabaseUseBuiltInRecoveryIfSupported);
+    if (!recovery_was_attempted) {
+      LOG(ERROR)
+          << "Recovery should have been attempted if the checks above passed.";
+      base::debug::DumpWithoutCrashing();
+      return;
+    }
+    // Recovery was attempted. The database handle has been poisoned and the
+    // error callback has been reset.
+
+    // Since the database was recovered from corruption, it's possible that some
+    // data in the newly recovered database is incorrect. When re-opening the
+    // database, we should attempt to fix any broken constraints.
+    //
+    // Unlike below when using sql::Recovery, which runs `FixTopSitesTable()` on
+    // the recovered copy of the database before overwriting the original
+    // (corrupted) database, here we defer the fix-up logic to after we've
+    // re-opened the database and all the other checks in the Init() method
+    // (version, schema, etc) pass.
+    //
+    // Note that recovery is only run when we detect corruption, but undetected
+    // corruption can happen at any time. We could consider running
+    // `FixTopSitesTable()` every time the database is opened, but in most cases
+    // that would be a (possibly expensive) no-op.
+    needs_fixing_up_ = true;
+
+    // Signal the test-expectation framework that the error was handled.
+    std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
+    return;
+  }
+
   if (sql::Recovery::ShouldRecover(extended_error)) {
     // Prevent reentrant calls.
     db_->reset_error_callback();
@@ -172,21 +212,6 @@
     return;
   }
 
-  // TODO(shess): This database's error histograms look like:
-  // 84% SQLITE_CORRUPT, SQLITE_CANTOPEN, SQLITE_NOTADB
-  //  7% SQLITE_ERROR
-  //  6% SQLITE_IOERR variants
-  //  2% SQLITE_READONLY
-  // .4% SQLITE_FULL
-  // nominal SQLITE_TOBIG, SQLITE_AUTH, and SQLITE_BUSY.  In the case of
-  // thumbnail_database.cc, as soon as the recovery code landed, SQLITE_IOERR
-  // shot to leadership.  If the I/O error is system-level, there is probably no
-  // hope, but if it is restricted to something about the database file, it is
-  // possible that the recovery code could be brought to bear.  In fact, it is
-  // possible that running recovery would be a reasonable default when errors
-  // are seen.
-
-  // The default handling is to assert on debug and to ignore on release.
   if (!sql::Database::IsExpectedSqliteError(extended_error))
     DLOG(FATAL) << db_->GetErrorMessage();
 }
@@ -306,6 +331,15 @@
   if (meta_table.GetVersionNumber() != kVersionNumber)
     return false;
 
+  // Attempt to fix up the table if recovery was attempted when opening.
+  if (needs_fixing_up_) {
+    CHECK(base::FeatureList::IsEnabled(
+        kTopSitesDatabaseUseBuiltInRecoveryIfSupported));
+
+    FixTopSitesTable(*db_);
+    needs_fixing_up_ = false;
+  }
+
   // Initialization is complete.
   return transaction.Commit();
 }
diff --git a/components/history/core/browser/top_sites_database.h b/components/history/core/browser/top_sites_database.h
index c685aaa..fd3bb0f 100644
--- a/components/history/core/browser/top_sites_database.h
+++ b/components/history/core/browser/top_sites_database.h
@@ -98,6 +98,11 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
+  // If recovery is attempted during one of the preliminary open attempts, the
+  // database should be checked for broken constraints. See comment in the
+  // DatabaseErrorCallback for more details.
+  bool needs_fixing_up_ = false;
+
   std::unique_ptr<sql::Database> db_ GUARDED_BY_CONTEXT(sequence_checker_);
 };
 
diff --git a/components/history/core/browser/top_sites_database_unittest.cc b/components/history/core/browser/top_sites_database_unittest.cc
index d8be1ae..e8b9e8a 100644
--- a/components/history/core/browser/top_sites_database_unittest.cc
+++ b/components/history/core/browser/top_sites_database_unittest.cc
@@ -2,13 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "components/history/core/browser/top_sites_database.h"
+
 #include <stddef.h>
 
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/history/core/browser/features.h"
 #include "components/history/core/browser/history_types.h"
-#include "components/history/core/browser/top_sites_database.h"
 #include "components/history/core/test/database_test_utils.h"
 #include "components/history/core/test/thumbnail-inl.h"
 #include "sql/database.h"
@@ -75,6 +78,20 @@
   base::FilePath file_name_;
 };
 
+// Tests both the legacy `sql::Recovery` interface and the newer
+// `sql::BuiltInRecovery` interface, if it's supported.
+class TopSitesDatabaseRecoveryTest : public TopSitesDatabaseTest,
+                                     public testing::WithParamInterface<bool> {
+ public:
+  TopSitesDatabaseRecoveryTest() {
+    scoped_feature_list_.InitWithFeatureState(
+        kTopSitesDatabaseUseBuiltInRecoveryIfSupported, GetParam());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
 // Version 1 is deprecated, the resulting schema should be current,
 // with no data.
 TEST_F(TopSitesDatabaseTest, Version1) {
@@ -154,7 +171,7 @@
 
 // Version 1 is deprecated, the resulting schema should be current, with no
 // data.
-TEST_F(TopSitesDatabaseTest, Recovery1) {
+TEST_P(TopSitesDatabaseRecoveryTest, Recovery1) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
 
@@ -174,22 +191,20 @@
   }
 
   // Corruption should be detected and recovered during Init().
+  TopSitesDatabase db;
   {
-    TopSitesDatabase db;
-    {
-      sql::test::ScopedErrorExpecter expecter;
-      expecter.ExpectError(SQLITE_CORRUPT);
-      ASSERT_TRUE(db.Init(file_name_));
-      EXPECT_TRUE(expecter.SawExpectedErrors());
-    }
-    VerifyTablesAndColumns(db.db_for_testing());
-    VerifyDatabaseEmpty(db.db_for_testing());
+    sql::test::ScopedErrorExpecter expecter;
+    expecter.ExpectError(SQLITE_CORRUPT);
+    ASSERT_TRUE(db.Init(file_name_));
+    EXPECT_TRUE(expecter.SawExpectedErrors());
   }
+  VerifyTablesAndColumns(db.db_for_testing());
+  VerifyDatabaseEmpty(db.db_for_testing());
 }
 
 // Version 2 is deprecated, the resulting schema should be current, with no
 // data.
-TEST_F(TopSitesDatabaseTest, Recovery2) {
+TEST_P(TopSitesDatabaseRecoveryTest, Recovery2) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
 
@@ -209,20 +224,18 @@
   }
 
   // Corruption should be detected and recovered during Init().
+  TopSitesDatabase db;
   {
-    TopSitesDatabase db;
-    {
-      sql::test::ScopedErrorExpecter expecter;
-      expecter.ExpectError(SQLITE_CORRUPT);
-      ASSERT_TRUE(db.Init(file_name_));
-      EXPECT_TRUE(expecter.SawExpectedErrors());
-    }
-    VerifyTablesAndColumns(db.db_for_testing());
-    VerifyDatabaseEmpty(db.db_for_testing());
+    sql::test::ScopedErrorExpecter expecter;
+    expecter.ExpectError(SQLITE_CORRUPT);
+    ASSERT_TRUE(db.Init(file_name_));
+    EXPECT_TRUE(expecter.SawExpectedErrors());
   }
+  VerifyTablesAndColumns(db.db_for_testing());
+  VerifyDatabaseEmpty(db.db_for_testing());
 }
 
-TEST_F(TopSitesDatabaseTest, Recovery4_CorruptHeader) {
+TEST_P(TopSitesDatabaseRecoveryTest, Recovery4_CorruptHeader) {
   // Create an example database.
   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
 
@@ -264,7 +277,7 @@
   }
 }
 
-TEST_F(TopSitesDatabaseTest, Recovery5_CorruptIndex) {
+TEST_P(TopSitesDatabaseRecoveryTest, Recovery5_CorruptIndex) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v5.sql"));
 
@@ -281,23 +294,20 @@
   }
 
   // Open the database and access the corrupt index.
+  TopSitesDatabase db;
+  ASSERT_TRUE(db.Init(file_name_));
+
   {
-    TopSitesDatabase db;
-    ASSERT_TRUE(db.Init(file_name_));
+    sql::test::ScopedErrorExpecter expecter;
+    expecter.ExpectError(SQLITE_CORRUPT);
 
-    {
-      sql::test::ScopedErrorExpecter expecter;
-      expecter.ExpectError(SQLITE_CORRUPT);
+    // Accessing the index will throw SQLITE_CORRUPT. The corruption handler
+    // will recover the database and poison the handle, so the outer call
+    // fails.
+    EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
+              db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
 
-      // Accessing the index will throw SQLITE_CORRUPT. The corruption handler
-      // will recover the database and poison the handle, so the outer call
-      // fails.
-      EXPECT_EQ(
-          TopSitesDatabase::kRankOfNonExistingURL,
-          db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
-
-      EXPECT_TRUE(expecter.SawExpectedErrors());
-    }
+    EXPECT_TRUE(expecter.SawExpectedErrors());
   }
 
   // Check that the database is recovered at the SQLite level.
@@ -309,27 +319,24 @@
 
   // After recovery, the database accesses won't throw errors. Recovery should
   // have regenerated the index with no data loss.
-  {
-    TopSitesDatabase db;
-    ASSERT_TRUE(db.Init(file_name_));
-    VerifyTablesAndColumns(db.db_for_testing());
+  ASSERT_TRUE(db.Init(file_name_));
+  VerifyTablesAndColumns(db.db_for_testing());
 
-    EXPECT_EQ(0,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
-    EXPECT_EQ(1,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
-    EXPECT_EQ(2,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl2, std::u16string())));
+  EXPECT_EQ(0,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
+  EXPECT_EQ(1,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
+  EXPECT_EQ(2,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl2, std::u16string())));
 
-    MostVisitedURLList urls = db.GetSites();
-    ASSERT_EQ(3u, urls.size());
-    EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
-    EXPECT_EQ(kUrl1, urls[1].url);  // [1] because of url_rank.
-    EXPECT_EQ(kUrl2, urls[2].url);  // [2] because of url_rank.
-  }
+  MostVisitedURLList urls = db.GetSites();
+  ASSERT_EQ(3u, urls.size());
+  EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
+  EXPECT_EQ(kUrl1, urls[1].url);  // [1] because of url_rank.
+  EXPECT_EQ(kUrl2, urls[2].url);  // [2] because of url_rank.
 }
 
-TEST_F(TopSitesDatabaseTest, Recovery5_CorruptIndexAndLostRow) {
+TEST_P(TopSitesDatabaseRecoveryTest, Recovery5_CorruptIndexAndLostRow) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v5.sql"));
 
@@ -354,23 +361,20 @@
   }
 
   // Open the database and access the corrupt index.
+  TopSitesDatabase db;
+  ASSERT_TRUE(db.Init(file_name_));
+
   {
-    TopSitesDatabase db;
-    ASSERT_TRUE(db.Init(file_name_));
+    sql::test::ScopedErrorExpecter expecter;
+    expecter.ExpectError(SQLITE_CORRUPT);
 
-    {
-      sql::test::ScopedErrorExpecter expecter;
-      expecter.ExpectError(SQLITE_CORRUPT);
+    // Accessing the index will throw SQLITE_CORRUPT. The corruption handler
+    // will recover the database and poison the handle, so the outer call
+    // fails.
+    EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
+              db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
 
-      // Accessing the index will throw SQLITE_CORRUPT. The corruption handler
-      // will recover the database and poison the handle, so the outer call
-      // fails.
-      EXPECT_EQ(
-          TopSitesDatabase::kRankOfNonExistingURL,
-          db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
-
-      EXPECT_TRUE(expecter.SawExpectedErrors());
-    }
+    EXPECT_TRUE(expecter.SawExpectedErrors());
   }
 
   // Check that the database is recovered at the SQLite level.
@@ -382,23 +386,20 @@
 
   // After recovery, the database accesses won't throw errors. Recovery should
   // have regenerated the index and adjusted the ranks.
-  {
-    TopSitesDatabase db;
-    ASSERT_TRUE(db.Init(file_name_));
-    VerifyTablesAndColumns(db.db_for_testing());
+  ASSERT_TRUE(db.Init(file_name_));
+  VerifyTablesAndColumns(db.db_for_testing());
 
-    EXPECT_EQ(0,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
-    EXPECT_EQ(1,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl2, std::u16string())));
-    EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
-              db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
+  EXPECT_EQ(0,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl0, std::u16string())));
+  EXPECT_EQ(1,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl2, std::u16string())));
+  EXPECT_EQ(TopSitesDatabase::kRankOfNonExistingURL,
+            db.GetURLRankForTesting(MostVisitedURL(kUrl1, std::u16string())));
 
-    MostVisitedURLList urls = db.GetSites();
-    ASSERT_EQ(2u, urls.size());
-    EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
-    EXPECT_EQ(kUrl2, urls[1].url);  // [1] because of url_rank.
-  }
+  MostVisitedURLList urls = db.GetSites();
+  ASSERT_EQ(2u, urls.size());
+  EXPECT_EQ(kUrl0, urls[0].url);  // [0] because of url_rank.
+  EXPECT_EQ(kUrl2, urls[1].url);  // [1] because of url_rank.
 }
 
 TEST_F(TopSitesDatabaseTest, ApplyDelta_Delete) {
@@ -549,4 +550,6 @@
   }
 }
 
+INSTANTIATE_TEST_SUITE_P(All, TopSitesDatabaseRecoveryTest, testing::Bool());
+
 }  // namespace history
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
index 9ebcc6c..094e941 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -450,6 +450,7 @@
       response_error_ = result.error().error();
       return;
     }
+    provided_by_on_device_ = result.value().provided_by_on_device;
     auto response =
         ParsedAnyMetadata<proto::ComposeResponse>(result.value().response);
     if (result.value().is_complete) {
@@ -470,6 +471,7 @@
   raw_ptr<OnDeviceModelAccessController> access_controller_ = nullptr;
   std::vector<std::string> streamed_responses_;
   std::optional<std::string> response_received_;
+  std::optional<bool> provided_by_on_device_;
   std::unique_ptr<ModelQualityLogEntry> log_entry_received_;
   std::optional<OptimizationGuideModelExecutionError::ModelExecutionError>
       response_error_;
@@ -493,6 +495,7 @@
   EXPECT_TRUE(response_received_);
   const std::string expected_response = "Input: execute:foo\n";
   EXPECT_EQ(*response_received_, expected_response);
+  EXPECT_TRUE(*provided_by_on_device_);
   EXPECT_THAT(streamed_responses_, ElementsAre(expected_response));
   EXPECT_TRUE(log_entry_received_);
   EXPECT_GT(log_entry_received_->log_ai_data_request()
@@ -1178,6 +1181,7 @@
             "2z");
   EXPECT_FALSE(
       log_ai_data_request_passed_to_remote_->compose().has_response_data());
+  EXPECT_FALSE(provided_by_on_device_.has_value());
 }
 
 TEST_F(OnDeviceModelServiceControllerTest,
diff --git a/components/optimization_guide/core/model_execution/session_impl.cc b/components/optimization_guide/core/model_execution/session_impl.cc
index f4a223d7..49ec394 100644
--- a/components/optimization_guide/core/model_execution/session_impl.cc
+++ b/components/optimization_guide/core/model_execution/session_impl.cc
@@ -468,6 +468,7 @@
       StreamingResponse{
           .response = *output,
           .is_complete = is_complete,
+          .provided_by_on_device = true,
       },
       std::move(log_entry));
 }
diff --git a/components/optimization_guide/core/optimization_guide_model_executor.h b/components/optimization_guide/core/optimization_guide_model_executor.h
index f5196d1..0319f0d 100644
--- a/components/optimization_guide/core/optimization_guide_model_executor.h
+++ b/components/optimization_guide/core/optimization_guide_model_executor.h
@@ -28,6 +28,9 @@
 
   // True if streaming has finished.
   bool is_complete = false;
+
+  // True if the response was computed on-device.
+  bool provided_by_on_device = false;
 };
 
 using OptimizationGuideModelStreamingExecutionResult =
diff --git a/components/policy/test/data/pref_mapping/EssentialSearchEnabled.json b/components/policy/test/data/pref_mapping/EssentialSearchEnabled.json
index 31997fe2..238eb170 100644
--- a/components/policy/test/data/pref_mapping/EssentialSearchEnabled.json
+++ b/components/policy/test/data/pref_mapping/EssentialSearchEnabled.json
@@ -1,5 +1,38 @@
 [
   {
-    "reason_for_missing_test": "TODO(b/275929724): Feature not yet implemented"
+    "os": [
+      "chromeos_ash",
+      "chromeos_lacros"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "essential_search_enabled": {
+            "default_value": false
+          }
+        }
+      },
+      {
+        "policies": {
+          "EssentialSearchEnabled": false
+        },
+        "prefs": {
+          "essential_search_enabled": {
+            "value": false
+          }
+        }
+      },
+      {
+        "policies": {
+          "EssentialSearchEnabled": true
+        },
+        "prefs": {
+          "essential_search_enabled": {
+            "value": true
+          }
+        }
+      }
+    ]
   }
 ]
diff --git a/components/push_notification/BUILD.gn b/components/push_notification/BUILD.gn
index 762cbc6..e970cc7c 100644
--- a/components/push_notification/BUILD.gn
+++ b/components/push_notification/BUILD.gn
@@ -14,3 +14,13 @@
   ]
   deps = [ "//base" ]
 }
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "push_notification_client_manager_unittest.cc" ]
+  deps = [
+    ":push_notification",
+    "//base/test:test_support",
+    "//testing/gtest",
+  ]
+}
diff --git a/components/push_notification/push_notification_client.h b/components/push_notification/push_notification_client.h
index 472df28..80d916a 100644
--- a/components/push_notification/push_notification_client.h
+++ b/components/push_notification/push_notification_client.h
@@ -6,21 +6,23 @@
 #define COMPONENTS_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_H_
 
 #include <string>
+
+#include "base/containers/flat_map.h"
 #include "components/push_notification/push_notification_client_id.h"
 
 namespace push_notification {
 
 // Base class for PushNotificationService clients to. Each feature that uses the
-// PushNotificationService must create it's own PushNotificationClient and
+// PushNotificationService must create its own PushNotificationClient and
 // implement the OnMessageReceived functionality. PushNotificationClient must
 // register with the PushNotificationService to begin receiving messages.
 class PushNotificationClient {
  public:
-  explicit PushNotificationClient(ClientId client_id);
-  virtual ~PushNotificationClient() = 0;
+  explicit PushNotificationClient(ClientId client_id) : client_id_(client_id) {}
+  virtual ~PushNotificationClient() = default;
 
   // Returns the feature's `client_id_`.
-  const ClientId& GetClientId() { return client_id_; }
+  ClientId GetClientId() { return client_id_; }
 
   // Only called when the instance of the `message_data` matches the
   // `client_id_`.
diff --git a/components/push_notification/push_notification_client_id.cc b/components/push_notification/push_notification_client_id.cc
index 54ca945..a98a462 100644
--- a/components/push_notification/push_notification_client_id.cc
+++ b/components/push_notification/push_notification_client_id.cc
@@ -15,11 +15,11 @@
   }
 }
 
-ClientId GetClientIdFromStr(const std::string& id_string) {
+std::optional<ClientId> GetClientIdFromStr(const std::string& id_string) {
   if (id_string == kNearbyPresenceClientId) {
     return ClientId::kNearbyPresence;
   }
-  return ClientId::kInvalidClientId;
+  return std::nullopt;
 }
 
 }  // namespace push_notification
diff --git a/components/push_notification/push_notification_client_id.h b/components/push_notification/push_notification_client_id.h
index c78bdb3..adc490f 100644
--- a/components/push_notification/push_notification_client_id.h
+++ b/components/push_notification/push_notification_client_id.h
@@ -12,13 +12,12 @@
 
 enum class ClientId {
   kNearbyPresence,
-  kInvalidClientId,
 };
 
 static constexpr char kNearbyPresenceClientId[] = "nearby_presence";
 
 std::optional<std::string> GetClientIdStr(ClientId id);
-ClientId GetClientIdFromStr(const std::string& id_string);
+std::optional<ClientId> GetClientIdFromStr(const std::string& id_string);
 
 }  // namespace push_notification
 
diff --git a/components/push_notification/push_notification_client_manager.cc b/components/push_notification/push_notification_client_manager.cc
index db24eb6c..3a2402c 100644
--- a/components/push_notification/push_notification_client_manager.cc
+++ b/components/push_notification/push_notification_client_manager.cc
@@ -4,22 +4,31 @@
 
 #include "components/push_notification/push_notification_client_manager.h"
 
+#include "base/logging.h"
+
+namespace {
+
+const char kClientIdKey[] = "type_id";
+
+}  // namespace
+
 namespace push_notification {
 
-PushNotificationClientManager::PushNotificationClientManager() = default;
+PushNotificationClientManager::PushNotificationClientManager() {}
+
 PushNotificationClientManager::~PushNotificationClientManager() = default;
 
 void PushNotificationClientManager::AddPushNotificationClient(
     PushNotificationClient* client) {
   CHECK(client);
-  // TODO(b/287340843): implement adding a PushNotification client to the
-  // `clients` map.
+  CHECK(GetClientIdStr(client->GetClientId()));
+  client_id_to_client_map_.insert_or_assign(client->GetClientId(), client);
+  FlushPendingMessageStore(client->GetClientId());
 }
 
 void PushNotificationClientManager::RemovePushNotificationClient(
     ClientId client_id) {
-  // TODO(b/287340843): implement removing a PushNotification client from the
-  // `clients` map.
+  client_id_to_client_map_.erase(client_id);
 }
 
 std::vector<const PushNotificationClient*>
@@ -31,12 +40,50 @@
   return client_list;
 }
 
+void PushNotificationClientManager::NotifyPushNotificationClientOfMessage(
+    PushNotificationMessage message) {
+  auto client_id = GetClientIdFromStr(message.data.at(kClientIdKey));
+
+  if (!client_id.has_value()) {
+    VLOG(1)
+        << __func__
+        << "Received a message for an invalid client ID. Message discarded.";
+    return;
+  }
+
+  if (client_id_to_client_map_.contains(client_id)) {
+    client_id_to_client_map_.at(client_id)->OnMessageReceived(message.data);
+  } else {
+    pending_message_store_.push_back(std::move(message));
+  }
+}
+
+void PushNotificationClientManager::FlushPendingMessageStore(
+    ClientId client_id) {
+  // Iterate through the `pending_message_store`, dispatch any messages that
+  // belong to the client being registered and remove dispatched messages.
+  auto iter = pending_message_store_.begin();
+  std::optional<std::string> client_id_str = GetClientIdStr(client_id);
+  if (!client_id_str.has_value()) {
+    return;
+  }
+  while (iter != pending_message_store_.end()) {
+    if (iter->data.at(kClientIdKey) == client_id_str) {
+      client_id_to_client_map_.at(client_id)->OnMessageReceived(iter->data);
+      iter = pending_message_store_.erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+}
+
 PushNotificationClientManager::PushNotificationMessage::
     PushNotificationMessage() = default;
-
 PushNotificationClientManager::PushNotificationMessage::PushNotificationMessage(
     PushNotificationMessage&& other) = default;
-
+PushNotificationClientManager::PushNotificationMessage&
+PushNotificationClientManager::PushNotificationMessage::operator=(
+    PushNotificationMessage&& other) = default;
 PushNotificationClientManager::PushNotificationMessage::
     ~PushNotificationMessage() = default;
 
diff --git a/components/push_notification/push_notification_client_manager.h b/components/push_notification/push_notification_client_manager.h
index 5f294a18..e26b914 100644
--- a/components/push_notification/push_notification_client_manager.h
+++ b/components/push_notification/push_notification_client_manager.h
@@ -6,22 +6,28 @@
 #define COMPONENTS_PUSH_NOTIFICATION_PUSH_NOTIFICATION_CLIENT_MANAGER_H_
 
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "components/push_notification/push_notification_client.h"
 
 namespace push_notification {
 
-// PushNotificationClientManager is responsible for delegating notifications to
-// the corresponding features who have a registered PushNotificationClient.
+// `PushNotificationClientManager` is responsible for delegating notifications
+// to the corresponding features who have a registered `PushNotificationClient`.
+// Messages received before the corresponding `PushNotificationClient` are
+// delivered to that client once registered via the client calling
+// `CheckPendingMessageStore()`.
 class PushNotificationClientManager {
  public:
   PushNotificationClientManager();
-  virtual ~PushNotificationClientManager();
+  ~PushNotificationClientManager();
 
   struct PushNotificationMessage {
     PushNotificationMessage();
     PushNotificationMessage(const PushNotificationMessage& other) = delete;
     PushNotificationMessage(PushNotificationMessage&& other);
-    virtual ~PushNotificationMessage();
+    ~PushNotificationMessage();
+
+    PushNotificationMessage& operator=(PushNotificationMessage&& other);
 
     // Key Value map to contain all relevant information intended for the
     // `PushNotificationClient`.
@@ -38,14 +44,19 @@
 
   void AddPushNotificationClient(PushNotificationClient* client);
   void RemovePushNotificationClient(ClientId client_id);
-
   std::vector<const PushNotificationClient*> GetPushNotificationClients();
+  void NotifyPushNotificationClientOfMessage(PushNotificationMessage message);
 
-  virtual void NotifyPushNotificationClientOfMessage(
-      PushNotificationMessage message) = 0;
+ private:
+  void FlushPendingMessageStore(ClientId client_id);
 
- protected:
   base::flat_map<ClientId, PushNotificationClient*> client_id_to_client_map_;
+
+  // Messages for clients that have not registered with the service yet. After a
+  // client registers, `FlushPendingMessageStore()` iterates through this vector
+  // and checks if there are any pending messages which should immediately be
+  // directed to the new client.
+  std::vector<PushNotificationMessage> pending_message_store_;
 };
 
 }  // namespace push_notification
diff --git a/components/push_notification/push_notification_client_manager_unittest.cc b/components/push_notification/push_notification_client_manager_unittest.cc
new file mode 100644
index 0000000..2f68fa7
--- /dev/null
+++ b/components/push_notification/push_notification_client_manager_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2024 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/push_notification/push_notification_client_manager.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestMessage[] = "This is a test message";
+const char kNotificationTypeIdKey[] = "type_id";
+const char kNotificationPayloadKey[] = "payload";
+
+class FakePushNotificationClient
+    : public push_notification::PushNotificationClient {
+ public:
+  explicit FakePushNotificationClient(
+      const push_notification::ClientId& client_id)
+      : push_notification::PushNotificationClient(client_id) {}
+  FakePushNotificationClient(const FakePushNotificationClient&) = delete;
+  FakePushNotificationClient& operator=(const FakePushNotificationClient&) =
+      delete;
+  ~FakePushNotificationClient() override = default;
+
+  void OnMessageReceived(
+      base::flat_map<std::string, std::string> message_data) override {
+    last_received_message_ = message_data.at(kNotificationPayloadKey);
+  }
+
+  const std::string& GetMostRecentMessageRecieved() {
+    return last_received_message_;
+  }
+
+ private:
+  std::string last_received_message_;
+};
+
+}  // namespace
+
+namespace push_notification {
+
+class PushNotificationClientManagerTest : public testing::Test {
+ public:
+  PushNotificationClientManagerTest() = default;
+  ~PushNotificationClientManagerTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    push_notification_client_manager_ =
+        std::make_unique<PushNotificationClientManager>();
+  }
+
+  std::unique_ptr<PushNotificationClientManager>
+      push_notification_client_manager_;
+};
+
+TEST_F(PushNotificationClientManagerTest, AddClient) {
+  auto fake_push_notification_client =
+      std::make_unique<FakePushNotificationClient>(
+          push_notification::ClientId::kNearbyPresence);
+  push_notification_client_manager_->AddPushNotificationClient(
+      fake_push_notification_client.get());
+
+  EXPECT_EQ(
+      1u,
+      push_notification_client_manager_->GetPushNotificationClients().size());
+}
+
+TEST_F(PushNotificationClientManagerTest, AddThenRemoveClient) {
+  auto fake_push_notification_client =
+      std::make_unique<FakePushNotificationClient>(
+          push_notification::ClientId::kNearbyPresence);
+  push_notification_client_manager_->AddPushNotificationClient(
+      fake_push_notification_client.get());
+  EXPECT_EQ(
+      1u,
+      push_notification_client_manager_->GetPushNotificationClients().size());
+  push_notification_client_manager_->RemovePushNotificationClient(
+      push_notification::ClientId::kNearbyPresence);
+  EXPECT_EQ(
+      0u,
+      push_notification_client_manager_->GetPushNotificationClients().size());
+}
+
+TEST_F(PushNotificationClientManagerTest, PassPushNotificationMessageToClient) {
+  auto fake_push_notification_client =
+      std::make_unique<FakePushNotificationClient>(
+          push_notification::ClientId::kNearbyPresence);
+
+  push_notification_client_manager_->AddPushNotificationClient(
+      fake_push_notification_client.get());
+  EXPECT_EQ(
+      1u,
+      push_notification_client_manager_->GetPushNotificationClients().size());
+
+  PushNotificationClientManager::PushNotificationMessage test_incoming_message;
+  test_incoming_message.data.insert_or_assign(
+      kNotificationTypeIdKey, push_notification::kNearbyPresenceClientId);
+  test_incoming_message.data.insert_or_assign(kNotificationPayloadKey,
+                                              kTestMessage);
+
+  push_notification_client_manager_->NotifyPushNotificationClientOfMessage(
+      std::move(test_incoming_message));
+  EXPECT_EQ(kTestMessage,
+            fake_push_notification_client->GetMostRecentMessageRecieved());
+  ;
+}
+
+TEST_F(PushNotificationClientManagerTest,
+       PassPushNotificationMessageBeforeAddingClient) {
+  PushNotificationClientManager::PushNotificationMessage test_incoming_message;
+  test_incoming_message.data.insert_or_assign(
+      kNotificationTypeIdKey, push_notification::kNearbyPresenceClientId);
+  test_incoming_message.data.insert_or_assign(kNotificationPayloadKey,
+                                              kTestMessage);
+
+  push_notification_client_manager_->NotifyPushNotificationClientOfMessage(
+      std::move(test_incoming_message));
+
+  auto fake_push_notification_client =
+      std::make_unique<FakePushNotificationClient>(
+          push_notification::ClientId::kNearbyPresence);
+
+  push_notification_client_manager_->AddPushNotificationClient(
+      fake_push_notification_client.get());
+  EXPECT_EQ(
+      1u,
+      push_notification_client_manager_->GetPushNotificationClients().size());
+  EXPECT_EQ(kTestMessage,
+            fake_push_notification_client->GetMostRecentMessageRecieved());
+}
+
+}  // namespace push_notification
diff --git a/components/push_notification/push_notification_service.cc b/components/push_notification/push_notification_service.cc
index bdad767..d560eb2 100644
--- a/components/push_notification/push_notification_service.cc
+++ b/components/push_notification/push_notification_service.cc
@@ -6,7 +6,9 @@
 
 namespace push_notification {
 
-PushNotificationService::PushNotificationService() = default;
+PushNotificationService::PushNotificationService()
+    : client_manager_(std::make_unique<PushNotificationClientManager>()) {}
+
 PushNotificationService::~PushNotificationService() = default;
 
 PushNotificationClientManager*
diff --git a/components/user_manager/OWNERS b/components/user_manager/OWNERS
index 1e3e2653..52a5f18 100644
--- a/components/user_manager/OWNERS
+++ b/components/user_manager/OWNERS
@@ -1,5 +1,4 @@
 achuith@chromium.org
 alemate@chromium.org
 antrim@chromium.org
-rsorokin@google.com
 xiyuan@chromium.org
diff --git a/components/visitedlink/browser/visitedlink_writer.cc b/components/visitedlink/browser/visitedlink_writer.cc
index 6785fd9..1c47272 100644
--- a/components/visitedlink/browser/visitedlink_writer.cc
+++ b/components/visitedlink/browser/visitedlink_writer.cc
@@ -9,6 +9,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string_view>
 #include <utility>
 
 #include "base/files/file_util.h"
@@ -328,8 +329,7 @@
   if (!url.is_valid())
     return null_hash_;  // Don't add invalid URLs.
 
-  Fingerprint fingerprint =
-      ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_);
+  Fingerprint fingerprint = ComputeURLFingerprint(url.spec(), salt_);
   // If the table isn't loaded the table will be rebuilt and after
   // that accumulated fingerprints will be applied to the table.
   if (table_builder_.get() || table_is_loading_from_file_) {
@@ -424,8 +424,7 @@
       if (!url.is_valid())
         continue;
 
-      Fingerprint fingerprint =
-          ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_);
+      Fingerprint fingerprint = ComputeURLFingerprint(url.spec(), salt_);
       deleted_since_rebuild_.insert(fingerprint);
 
       // If the URL was just added and now we're deleting it, it may be in the
@@ -450,8 +449,7 @@
     const GURL& url(urls->NextURL());
     if (!url.is_valid())
       continue;
-    deleted_fingerprints.insert(
-        ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_));
+    deleted_fingerprints.insert(ComputeURLFingerprint(url.spec(), salt_));
   }
   DeleteFingerprintsFromCurrentTable(deleted_fingerprints);
 }
@@ -760,16 +758,14 @@
     // Also add anything that was added while we were asynchronously
     // loading the table.
     for (const GURL& url : added_since_load_) {
-      Fingerprint fingerprint =
-          ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_);
+      Fingerprint fingerprint = ComputeURLFingerprint(url.spec(), salt_);
       AddFingerprint(fingerprint, false);
     }
     added_since_load_.clear();
 
     // Now handle deletions.
     for (const GURL& url : deleted_since_load_) {
-      Fingerprint fingerprint =
-          ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_);
+      Fingerprint fingerprint = ComputeURLFingerprint(url.spec(), salt_);
       DeleteFingerprint(fingerprint, false);
     }
     deleted_since_load_.clear();
@@ -1160,8 +1156,8 @@
 
 void VisitedLinkWriter::TableBuilder::OnURL(const GURL& url) {
   if (!url.is_empty()) {
-    fingerprints_.push_back(VisitedLinkWriter::ComputeURLFingerprint(
-        url.spec().data(), url.spec().length(), salt_));
+    fingerprints_.push_back(
+        VisitedLinkWriter::ComputeURLFingerprint(url.spec(), salt_));
   }
 }
 
diff --git a/components/visitedlink/common/visitedlink_common.cc b/components/visitedlink/common/visitedlink_common.cc
index b0d455e..65e239984 100644
--- a/components/visitedlink/common/visitedlink_common.cc
+++ b/components/visitedlink/common/visitedlink_common.cc
@@ -7,6 +7,7 @@
 #include <string.h>  // for memset()
 
 #include <ostream>
+#include <string_view>
 
 #include "base/bit_cast.h"
 #include "base/check.h"
@@ -27,17 +28,18 @@
 
 // FIXME: this uses linear probing, it should be replaced with quadratic
 // probing or something better. See VisitedLinkWriter::AddFingerprint
-bool VisitedLinkCommon::IsVisited(const char* canonical_url,
-                                  size_t url_len) const {
-  if (url_len == 0)
+bool VisitedLinkCommon::IsVisited(std::string_view canonical_url) const {
+  if (canonical_url.size() == 0) {
     return false;
-  if (!hash_table_ || table_length_ == 0)
+  }
+  if (!hash_table_ || table_length_ == 0) {
     return false;
-  return IsVisited(ComputeURLFingerprint(canonical_url, url_len));
+  }
+  return IsVisited(ComputeURLFingerprint(canonical_url));
 }
 
 bool VisitedLinkCommon::IsVisited(const GURL& url) const {
-  return IsVisited(url.spec().data(), url.spec().size());
+  return IsVisited(url.spec());
 }
 
 bool VisitedLinkCommon::IsVisited(Fingerprint fingerprint) const {
@@ -77,16 +79,15 @@
 
 // static
 VisitedLinkCommon::Fingerprint VisitedLinkCommon::ComputeURLFingerprint(
-    const char* canonical_url,
-    size_t url_len,
+    std::string_view canonical_url,
     const uint8_t salt[LINK_SALT_LENGTH]) {
-  DCHECK(url_len > 0) << "Canonical URLs should not be empty";
+  DCHECK(canonical_url.size() > 0) << "Canonical URLs should not be empty";
 
   base::MD5Context ctx;
   base::MD5Init(&ctx);
   base::MD5Update(&ctx, base::StringPiece(reinterpret_cast<const char*>(salt),
                                           LINK_SALT_LENGTH));
-  base::MD5Update(&ctx, base::StringPiece(canonical_url, url_len));
+  base::MD5Update(&ctx, canonical_url);
 
   base::MD5Digest digest;
   base::MD5Final(&digest, &ctx);
diff --git a/components/visitedlink/common/visitedlink_common.h b/components/visitedlink/common/visitedlink_common.h
index 05a150a..d63728a1 100644
--- a/components/visitedlink/common/visitedlink_common.h
+++ b/components/visitedlink/common/visitedlink_common.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <string_view>
 #include <vector>
 
 #include "base/memory/raw_ptr.h"
@@ -66,15 +67,14 @@
   virtual ~VisitedLinkCommon();
 
   // Returns the fingerprint for the given URL.
-  Fingerprint ComputeURLFingerprint(const char* canonical_url,
-                                    size_t url_len) const {
-    return ComputeURLFingerprint(canonical_url, url_len, salt_);
+  Fingerprint ComputeURLFingerprint(std::string_view canonical_url) const {
+    return ComputeURLFingerprint(canonical_url, salt_);
   }
 
   // Looks up the given key in the table. The fingerprint for the URL is
   // computed if you call one with the string argument. Returns true if found.
   // Does not modify the hastable.
-  bool IsVisited(const char* canonical_url, size_t url_len) const;
+  bool IsVisited(const std::string_view canonical_url) const;
   bool IsVisited(const GURL& url) const;
   bool IsVisited(Fingerprint fingerprint) const;
 
@@ -119,8 +119,7 @@
   // pass the salt as a parameter. See the non-static version above if you
   // want to use the current class' salt.
   static Fingerprint ComputeURLFingerprint(
-      const char* canonical_url,
-      size_t url_len,
+      std::string_view canonical_url,
       const uint8_t salt[LINK_SALT_LENGTH]);
 
   // Computes the hash value of the given fingerprint, this is used as a lookup
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index f1e29506..a9ba95dd 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -261,7 +261,12 @@
 // callbacks to match the preferred framerate.
 BASE_FEATURE(kOnBeginFrameThrottleVideo,
              "OnBeginFrameThrottleVideo",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+#if BUILDFLAG(IS_ANDROID)
+             base::FEATURE_ENABLED_BY_DEFAULT
+#else
+             base::FEATURE_DISABLED_BY_DEFAULT
+#endif
+             );
 
 BASE_FEATURE(kSharedBitmapToSharedImage,
              "SharedBitmapToSharedImage",
diff --git a/components/viz/service/display/display_resource_provider_software.cc b/components/viz/service/display/display_resource_provider_software.cc
index 0696509..771f03b 100644
--- a/components/viz/service/display/display_resource_provider_software.cc
+++ b/components/viz/service/display/display_resource_provider_software.cc
@@ -56,6 +56,10 @@
       WaitSyncToken(resource->transferable.mailbox_holder.sync_token);
       access->representation =
           shared_image_manager_->ProduceMemory(mailbox, memory_tracker_.get());
+      if (!access->representation) {
+        return nullptr;
+      }
+
       access->read_access = access->representation->BeginScopedReadAccess();
       resource_shared_images_.emplace(id, std::move(access));
     }
@@ -80,13 +84,15 @@
   return resource;
 }
 
-void DisplayResourceProviderSoftware::UnlockForRead(ResourceId id) {
+void DisplayResourceProviderSoftware::UnlockForRead(ResourceId id,
+                                                    const SkImage* sk_image) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   ChildResource* resource = GetResource(id);
-
   DCHECK(!resource->is_gpu_resource_type());
-  DCHECK_GT(resource->lock_for_read_count, 0);
-  resource->lock_for_read_count--;
+  if (sk_image) {
+    DCHECK_GE(resource->lock_for_read_count, 0);
+    resource->lock_for_read_count--;
+  }
   TryReleaseResource(id, resource);
 }
 
@@ -159,7 +165,9 @@
     SkAlphaType alpha_type)
     : resource_provider_(resource_provider), resource_id_(resource_id) {
   const ChildResource* resource = resource_provider->LockForRead(resource_id);
-  DCHECK(resource);
+  if (!resource) {
+    return;
+  }
   DCHECK(!resource->is_gpu_resource_type());
 
   // Use cached SkImage if possible.
@@ -209,7 +217,7 @@
 
 DisplayResourceProviderSoftware::ScopedReadLockSkImage::
     ~ScopedReadLockSkImage() {
-  resource_provider_->UnlockForRead(resource_id_);
+  resource_provider_->UnlockForRead(resource_id_, sk_image_.get());
 }
 
 DisplayResourceProviderSoftware::SharedImageAccess::SharedImageAccess() =
diff --git a/components/viz/service/display/display_resource_provider_software.h b/components/viz/service/display/display_resource_provider_software.h
index b88e26a..d5595fef4 100644
--- a/components/viz/service/display/display_resource_provider_software.h
+++ b/components/viz/service/display/display_resource_provider_software.h
@@ -62,7 +62,7 @@
   // These functions are used by ScopedReadLockSkImage to lock and unlock
   // resources.
   const ChildResource* LockForRead(ResourceId id);
-  void UnlockForRead(ResourceId id);
+  void UnlockForRead(ResourceId id, const SkImage* sk_image);
 
   // DisplayResourceProvider overrides:
   std::vector<ReturnedResource> DeleteAndReturnUnusedResourcesToChildImpl(
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_id.cc b/components/web_package/signed_web_bundles/signed_web_bundle_id.cc
index 5527e98..be1f7d6 100644
--- a/components/web_package/signed_web_bundles/signed_web_bundle_id.cc
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_id.cc
@@ -85,9 +85,9 @@
 
 // static
 SignedWebBundleId SignedWebBundleId::CreateRandomForDevelopment(
-    base::RepeatingCallback<void(void*, size_t)> random_generator) {
+    base::RepeatingCallback<void(base::span<uint8_t>)> random_generator) {
   std::array<uint8_t, kDecodedIdLength - kTypeSuffixLength> random_bytes;
-  random_generator.Run(random_bytes.data(), random_bytes.size());
+  random_generator.Run(random_bytes);
   return CreateForDevelopment(random_bytes);
 }
 
@@ -104,9 +104,12 @@
 SignedWebBundleId::~SignedWebBundleId() = default;
 
 // static
-base::RepeatingCallback<void(void*, size_t)>
+base::RepeatingCallback<void(base::span<uint8_t>)>
 SignedWebBundleId::GetDefaultRandomGenerator() {
-  return base::BindRepeating(&base::RandBytes);
+  // TODO(crbug.com/1490484): Remove the static_cast once all callers of
+  // base::RandBytes are migrated to the span variant.
+  return base::BindRepeating(
+      static_cast<void (*)(base::span<uint8_t>)>(&base::RandBytes));
 }
 
 }  // namespace web_package
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_id.h b/components/web_package/signed_web_bundles/signed_web_bundle_id.h
index b6eb78f91..f1c155a 100644
--- a/components/web_package/signed_web_bundles/signed_web_bundle_id.h
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_id.h
@@ -5,6 +5,7 @@
 #ifndef COMPONENTS_WEB_PACKAGE_SIGNED_WEB_BUNDLES_SIGNED_WEB_BUNDLE_ID_H_
 #define COMPONENTS_WEB_PACKAGE_SIGNED_WEB_BUNDLES_SIGNED_WEB_BUNDLE_ID_H_
 
+#include "base/containers/span.h"
 #include "base/functional/callback.h"
 #include "base/strings/string_piece.h"
 #include "base/types/expected.h"
@@ -55,7 +56,7 @@
       base::span<const uint8_t, kDecodedIdLength - kTypeSuffixLength> data);
 
   static SignedWebBundleId CreateRandomForDevelopment(
-      base::RepeatingCallback<void(void*, size_t)> random_generator =
+      base::RepeatingCallback<void(base::span<uint8_t>)> random_generator =
           GetDefaultRandomGenerator());
 
   SignedWebBundleId(const SignedWebBundleId& other);
@@ -87,7 +88,7 @@
   std::string encoded_id_;
   std::array<uint8_t, kDecodedIdLength> decoded_id_;
 
-  static base::RepeatingCallback<void(void*, size_t)>
+  static base::RepeatingCallback<void(base::span<uint8_t>)>
   GetDefaultRandomGenerator();
 };
 
diff --git a/components/web_package/signed_web_bundles/signed_web_bundle_id_unittest.cc b/components/web_package/signed_web_bundles/signed_web_bundle_id_unittest.cc
index ed5630a..44b7a66 100644
--- a/components/web_package/signed_web_bundles/signed_web_bundle_id_unittest.cc
+++ b/components/web_package/signed_web_bundles/signed_web_bundle_id_unittest.cc
@@ -153,9 +153,9 @@
 
 TEST(SignedWebBundleIdTest, CreateRandomForDevelopmentCustomGenerator) {
   auto custom_callback =
-      base::BindLambdaForTesting([](void* ptr, size_t len) -> void {
-        DCHECK_EQ(len, kDevelopmentBytes.size());
-        base::ranges::copy(kDevelopmentBytes, static_cast<uint8_t*>(ptr));
+      base::BindLambdaForTesting([](base::span<uint8_t> buffer) -> void {
+        DCHECK_EQ(buffer.size(), kDevelopmentBytes.size());
+        base::ranges::copy(kDevelopmentBytes, buffer.begin());
       });
 
   SignedWebBundleId id =
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index 03bcd106..729f4a2 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -3013,6 +3013,11 @@
       FILE_PATH_LITERAL("progress-with-background-exposes-values.html"));
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
+                       AccessibilityProgressMeterCrash) {
+  RunFormControlsTest(FILE_PATH_LITERAL("progress-meter-crash.html"));
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityQ) {
   RunHtmlTest(FILE_PATH_LITERAL("q.html"));
 }
diff --git a/content/browser/attribution_reporting/attribution_host.cc b/content/browser/attribution_reporting/attribution_host.cc
index ef2aa7f..74317e16 100644
--- a/content/browser/attribution_reporting/attribution_host.cc
+++ b/content/browser/attribution_reporting/attribution_host.cc
@@ -322,6 +322,11 @@
     return absl::nullopt;
   }
 
+  if (!render_frame_host->IsFeatureEnabled(
+          blink::mojom::PermissionsPolicyFeature::kAttributionReporting)) {
+    return absl::nullopt;
+  }
+
   return suitable_top_frame_origin;
 }
 
diff --git a/content/browser/attribution_reporting/attribution_host_unittest.cc b/content/browser/attribution_reporting/attribution_host_unittest.cc
index 467bf59b..0bfd986 100644
--- a/content/browser/attribution_reporting/attribution_host_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_host_unittest.cc
@@ -129,6 +129,16 @@
     fenced_frame_node->set_fenced_frame_properties(new_props);
   }
 
+  blink::ParsedPermissionsPolicy RestrictivePermissionsPolicy(
+      const url::Origin& allowed_origin) {
+    return {blink::ParsedPermissionsPolicyDeclaration(
+        blink::mojom::PermissionsPolicyFeature::kAttributionReporting,
+        /*allowed_origins=*/
+        {*blink::OriginWithPossibleWildcards::FromOrigin(allowed_origin)},
+        /*self_if_matches=*/absl::nullopt,
+        /*matches_all_origins=*/false, /*matches_opaque_src=*/false)};
+  }
+
   void ClearAttributionManager() {
     mock_data_host_manager_ = nullptr;
     OverrideAttributionManager(nullptr);
@@ -650,6 +660,7 @@
   static_cast<RenderFrameHostImpl*>(fenced_frame)
       ->frame_tree_node()
       ->SetFencedFramePropertiesOpaqueAdsModeForTesting();
+  SetFencedFrameConfigPermissions(fenced_frame);
   fenced_frame = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
       GURL("https://fencedframe.example"), fenced_frame);
   ScopedAttributionHostTargetFrame frame_scope(attribution_host(),
@@ -768,14 +779,8 @@
 
     auto simulator = NavigationSimulatorImpl::CreateRendererInitiated(
         GURL(test_case.fenced_frame_url), fenced_frame);
-    simulator->SetPermissionsPolicyHeader(
-        {blink::ParsedPermissionsPolicyDeclaration(
-            blink::mojom::PermissionsPolicyFeature::kAttributionReporting,
-            /*allowed_origins=*/
-            {*blink::OriginWithPossibleWildcards::FromOrigin(
-                url::Origin::Create(GURL(kAllowedOriginUrl)))},
-            /*self_if_matches=*/absl::nullopt,
-            /*matches_all_origins=*/false, /*matches_opaque_src=*/false)});
+    simulator->SetPermissionsPolicyHeader(RestrictivePermissionsPolicy(
+        url::Origin::Create(GURL(kAllowedOriginUrl))));
     simulator->Commit();
     fenced_frame = simulator->GetFinalRenderFrameHost();
 
@@ -806,12 +811,8 @@
 
     auto simulator1 = NavigationSimulatorImpl::CreateRendererInitiated(
         GURL(test_case.url), main_rfh());
-    simulator1->SetPermissionsPolicyHeader(
-        {blink::ParsedPermissionsPolicyDeclaration(
-            blink::mojom::PermissionsPolicyFeature::kAttributionReporting,
-            /*allowed_origins=*/{},
-            /*self_if_matches*/ url::Origin::Create(GURL(kAllowedOriginUrl)),
-            /*matches_all_origins=*/false, /*matches_opaque_src=*/false)});
+    simulator1->SetPermissionsPolicyHeader(RestrictivePermissionsPolicy(
+        url::Origin::Create(GURL(kAllowedOriginUrl))));
     simulator1->Commit();
 
     auto simulator2 = NavigationSimulatorImpl::CreateRendererInitiated(
@@ -822,6 +823,122 @@
   }
 }
 
+TEST_F(AttributionHostTest, RegisterDataHost_FeaturePolicyChecked) {
+  contents()->NavigateAndCommit(GURL("https://top.example"));
+
+  static constexpr char kAllowedOriginUrl[] = "https://a.test";
+
+  const struct {
+    const char* subframe_url;
+    bool expected;
+  } kTestCases[] = {
+      {kAllowedOriginUrl, true},
+      {"https://b.test", false},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    EXPECT_CALL(*mock_data_host_manager(), RegisterDataHost)
+        .Times(test_case.expected);
+
+    content::RenderFrameHostTester* rfh_tester =
+        content::RenderFrameHostTester::For(main_rfh());
+    content::RenderFrameHost* subframe = rfh_tester->AppendChildWithPolicy(
+        "subframe", RestrictivePermissionsPolicy(
+                        url::Origin::Create(GURL(kAllowedOriginUrl))));
+    subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
+        GURL(test_case.subframe_url), subframe);
+    ScopedAttributionHostTargetFrame frame_scope(attribution_host(), subframe);
+
+    mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
+    attribution_host_mojom()->RegisterDataHost(
+        data_host_remote.BindNewPipeAndPassReceiver(),
+        RegistrationEligibility::kSource);
+
+    base::RunLoop().RunUntilIdle();
+  }
+}
+TEST_F(AttributionHostTest, RegisterNavigationDataHost_FeaturePolicyChecked) {
+  contents()->NavigateAndCommit(GURL("https://top.example"));
+
+  static constexpr char kAllowedOriginUrl[] = "https://a.test";
+
+  const struct {
+    const char* subframe_url;
+    bool expected;
+  } kTestCases[] = {
+      {kAllowedOriginUrl, true},
+      {"https://b.test", false},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    if (test_case.expected) {
+      EXPECT_CALL(*mock_data_host_manager(), RegisterNavigationDataHost)
+          .WillOnce(Return(true));
+    } else {
+      EXPECT_CALL(*mock_data_host_manager(), RegisterNavigationDataHost)
+          .Times(0);
+    }
+
+    content::RenderFrameHostTester* rfh_tester =
+        content::RenderFrameHostTester::For(main_rfh());
+    content::RenderFrameHost* subframe = rfh_tester->AppendChildWithPolicy(
+        "subframe", RestrictivePermissionsPolicy(
+                        url::Origin::Create(GURL(kAllowedOriginUrl))));
+    subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
+        GURL(test_case.subframe_url), subframe);
+    ScopedAttributionHostTargetFrame frame_scope(attribution_host(), subframe);
+
+    mojo::Remote<blink::mojom::AttributionDataHost> data_host_remote;
+    attribution_host_mojom()->RegisterNavigationDataHost(
+        data_host_remote.BindNewPipeAndPassReceiver(),
+        blink::AttributionSrcToken());
+
+    base::RunLoop().RunUntilIdle();
+  }
+}
+TEST_F(
+    AttributionHostTest,
+    NotifyNavigationWithBackgroundRegistrationsWillStart_FeaturePolicyChecked) {
+  contents()->NavigateAndCommit(GURL("https://top.example"));
+
+  static constexpr char kAllowedOriginUrl[] = "https://a.test";
+
+  const struct {
+    const char* subframe_url;
+    bool expected;
+  } kTestCases[] = {
+      {kAllowedOriginUrl, true},
+      {"https://b.test", false},
+  };
+
+  for (const auto& test_case : kTestCases) {
+    if (test_case.expected) {
+      EXPECT_CALL(*mock_data_host_manager(),
+                  NotifyNavigationWithBackgroundRegistrationsWillStart)
+          .WillOnce(Return(true));
+    } else {
+      EXPECT_CALL(*mock_data_host_manager(),
+                  NotifyNavigationWithBackgroundRegistrationsWillStart)
+          .Times(0);
+    }
+
+    content::RenderFrameHostTester* rfh_tester =
+        content::RenderFrameHostTester::For(main_rfh());
+    content::RenderFrameHost* subframe = rfh_tester->AppendChildWithPolicy(
+        "subframe", RestrictivePermissionsPolicy(
+                        url::Origin::Create(GURL(kAllowedOriginUrl))));
+    subframe = NavigationSimulatorImpl::NavigateAndCommitFromDocument(
+        GURL(test_case.subframe_url), subframe);
+    ScopedAttributionHostTargetFrame frame_scope(attribution_host(), subframe);
+
+    attribution_host_mojom()
+        ->NotifyNavigationWithBackgroundRegistrationsWillStart(
+            blink::AttributionSrcToken(), /*expected_registrations=*/1);
+
+    base::RunLoop().RunUntilIdle();
+  }
+}
+
 TEST_F(AttributionHostTest, InsecureTaintTracking) {
   blink::Impression impression;
 
diff --git a/content/browser/back_forward_cache_features_browsertest.cc b/content/browser/back_forward_cache_features_browsertest.cc
index 85fd7cc0..38ac662 100644
--- a/content/browser/back_forward_cache_features_browsertest.cc
+++ b/content/browser/back_forward_cache_features_browsertest.cc
@@ -4978,13 +4978,19 @@
   }
 };
 
-// TODO(crbug.com/1491942): This fails with the field trial testing config.
 class BackForwardCacheBrowserTestWithMediaSessionNoTestingConfig
     : public BackForwardCacheBrowserTestWithMediaSession {
  public:
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch("disable-field-trial-config");
     DisableFeature(features::kBackForwardCacheMediaSessionService);
+
+    // The MediaSessionEnterPictureInPicture feature depends on the
+    // BackForwardCacheMediaSessionService feature, so we need to also disable
+    // it here.
+    // TODO(https://crbug.com/1510995): Remove these tests since the
+    // BackForwardCacheMediaSessionService feature has been launched.
+    DisableFeature(blink::features::kMediaSessionEnterPictureInPicture);
+
     BackForwardCacheBrowserTestWithMediaSession::SetUpCommandLine(command_line);
   }
 };
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index cac23a8..6e9c975 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -177,6 +177,11 @@
 }
 
 // static
+MediaSession* MediaSession::GetIfExists(WebContents* contents) {
+  return MediaSessionImpl::FromWebContents(contents);
+}
+
+// static
 const base::UnguessableToken& MediaSession::GetSourceId(
     BrowserContext* browser_context) {
   return MediaSessionData::GetOrCreate(browser_context)->source_id();
@@ -231,6 +236,7 @@
     CreateForWebContents(web_contents);
     session = FromWebContents(web_contents);
     session->Initialize();
+    static_cast<WebContentsImpl*>(web_contents)->MediaSessionCreated(session);
   }
   return session;
 }
diff --git a/content/browser/renderer_host/navigation_entry_impl.cc b/content/browser/renderer_host/navigation_entry_impl.cc
index bca3f67..d3fd2fb 100644
--- a/content/browser/renderer_host/navigation_entry_impl.cc
+++ b/content/browser/renderer_host/navigation_entry_impl.cc
@@ -512,6 +512,14 @@
   return title_;
 }
 
+void NavigationEntryImpl::SetAppTitle(const std::u16string& app_title) {
+  app_title_ = app_title;
+}
+
+const std::u16string& NavigationEntryImpl::GetAppTitle() {
+  return app_title_;
+}
+
 void NavigationEntryImpl::SetPageState(const blink::PageState& state,
                                        NavigationEntryRestoreContext* context) {
   DCHECK(state.IsValid());
diff --git a/content/browser/renderer_host/navigation_entry_impl.h b/content/browser/renderer_host/navigation_entry_impl.h
index e48b6870..5c073400 100644
--- a/content/browser/renderer_host/navigation_entry_impl.h
+++ b/content/browser/renderer_host/navigation_entry_impl.h
@@ -138,6 +138,8 @@
   const GURL& GetVirtualURL() override;
   void SetTitle(const std::u16string& title) override;
   const std::u16string& GetTitle() override;
+  void SetAppTitle(const std::u16string& app_title) override;
+  const std::u16string& GetAppTitle() override;
   void SetPageState(const blink::PageState& state,
                     NavigationEntryRestoreContext* context) override;
   blink::PageState GetPageState() override;
@@ -535,6 +537,11 @@
   GURL virtual_url_;
   bool update_virtual_url_with_url_;
   std::u16string title_;
+  // The app title is optional and may be empty. If set to a non-empty value, a
+  // web app displayed in an app window may use this string instead of the
+  // regular title. See
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/DocumentSubtitle/explainer.md
+  std::u16string app_title_;
   FaviconStatus favicon_;
   SSLStatus ssl_;
   ui::PageTransition transition_type_;
diff --git a/content/browser/renderer_host/render_frame_host_delegate.h b/content/browser/renderer_host/render_frame_host_delegate.h
index 99619b7..472e4d0 100644
--- a/content/browser/renderer_host/render_frame_host_delegate.h
+++ b/content/browser/renderer_host/render_frame_host_delegate.h
@@ -255,6 +255,10 @@
                            const std::u16string& title,
                            base::i18n::TextDirection title_direction) {}
 
+  // Update app title.
+  virtual void UpdateAppTitle(RenderFrameHostImpl* render_frame_host,
+                              const std::u16string& app_title) {}
+
   // The destination URL has changed and should be updated.
   virtual void UpdateTargetURL(RenderFrameHostImpl* render_frame_host,
                                const GURL& url) {}
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index cc4841dc..77ff4f22 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -6661,6 +6661,11 @@
   delegate_->UpdateTitle(this, received_title, title_direction);
 }
 
+// Update app title.
+void RenderFrameHostImpl::UpdateAppTitle(const ::std::u16string& app_title) {
+  delegate_->UpdateAppTitle(this, app_title);
+}
+
 void RenderFrameHostImpl::DidInferColorScheme(
     blink::mojom::PreferredColorScheme color_scheme) {
   if (is_main_frame()) {
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 5ecccb9..500f4765 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -2337,6 +2337,7 @@
   void NavigateEventHandlerPresenceChanged(bool present) override;
   void UpdateTitle(const absl::optional<::std::u16string>& title,
                    base::i18n::TextDirection title_direction) override;
+  void UpdateAppTitle(const ::std::u16string& app_title) override;
   void UpdateUserActivationState(
       blink::mojom::UserActivationUpdateType update_type,
       blink::mojom::UserActivationNotificationType notification_type) override;
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index c65048c2..3c2d09a 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2195,6 +2195,10 @@
   return GetNavigationEntryForTitle()->GetTitleForDisplay();
 }
 
+const std::u16string& WebContentsImpl::GetAppTitle() {
+  return GetNavigationEntryForTitle()->GetAppTitle();
+}
+
 SiteInstanceImpl* WebContentsImpl::GetSiteInstance() {
   return GetRenderManager()->current_frame_host()->GetSiteInstance();
 }
@@ -8419,6 +8423,36 @@
   }
 }
 
+void WebContentsImpl::UpdateAppTitle(RenderFrameHostImpl* render_frame_host,
+                                     const std::u16string& app_title) {
+  OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::UpdateTitle",
+                        "render_frame_host", render_frame_host, "app_title",
+                        app_title);
+  // Same logic as UpdateTitle() above.
+  NavigationEntryImpl* entry =
+      render_frame_host->frame_tree()->controller().GetEntryWithUniqueID(
+          render_frame_host->nav_entry_id());
+  if (!entry) {
+    if (render_frame_host->GetParent() || !render_frame_host->frame_tree()
+                                               ->controller()
+                                               .GetLastCommittedEntry()
+                                               ->IsInitialEntry()) {
+      return;
+    }
+    entry =
+        render_frame_host->frame_tree()->controller().GetLastCommittedEntry();
+  }
+  std::u16string final_app_title;
+  base::TrimWhitespace(app_title, base::TRIM_ALL, &final_app_title);
+
+  if (final_app_title == entry->GetAppTitle()) {
+    return;
+  }
+
+  entry->SetAppTitle(final_app_title);
+  NotifyNavigationStateChanged(INVALIDATE_TYPE_TITLE);
+}
+
 void WebContentsImpl::UpdateTargetURL(RenderFrameHostImpl* render_frame_host,
                                       const GURL& url) {
   OPTIONAL_TRACE_EVENT2("content", "WebContentsImpl::UpdateTargetURL",
@@ -9712,6 +9746,11 @@
   observers_.NotifyObservers(&WebContentsObserver::MediaDestroyed, id);
 }
 
+void WebContentsImpl::MediaSessionCreated(MediaSession* media_session) {
+  observers_.NotifyObservers(&WebContentsObserver::MediaSessionCreated,
+                             media_session);
+}
+
 int WebContentsImpl::GetCurrentlyPlayingVideoCount() {
   return currently_playing_video_count_;
 }
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 3ba96d4a..e66c4b1e 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -121,6 +121,7 @@
 class BrowserPluginGuest;
 class FindRequestManager;
 class JavaScriptDialogManager;
+class MediaSession;
 class MediaWebContentsObserver;
 class NFCHost;
 class RenderFrameHost;
@@ -375,6 +376,7 @@
   bool IsWebContentsOnlyAccessibilityModeForTesting() override;
   bool IsFullAccessibilityModeForTesting() override;
   const std::u16string& GetTitle() override;
+  const std::u16string& GetAppTitle() override;
   void UpdateTitleForEntry(NavigationEntry* entry,
                            const std::u16string& title) override;
   SiteInstanceImpl* GetSiteInstance() override;
@@ -640,6 +642,12 @@
   void UpdateTitle(RenderFrameHostImpl* render_frame_host,
                    const std::u16string& title,
                    base::i18n::TextDirection title_direction) override;
+  // The app title is an alternative title. If non-empty, the browser may choose
+  // to use the app title instead of the regular title for a web app displayed
+  // in an app window. See
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/DocumentSubtitle/explainer.md
+  void UpdateAppTitle(RenderFrameHostImpl* render_frame_host,
+                      const std::u16string& app_title) override;
   void UpdateTargetURL(RenderFrameHostImpl* render_frame_host,
                        const GURL& url) override;
   bool IsNeverComposited() override;
@@ -1139,6 +1147,9 @@
   // Called by MediaWebContentsObserver when a media player is destroyed.
   void MediaDestroyed(const MediaPlayerId& id);
 
+  // Called by MediaSessionImpl when one is created and initialized for this.
+  void MediaSessionCreated(MediaSession* media_session);
+
   int GetCurrentlyPlayingVideoCount() override;
   absl::optional<gfx::Size> GetFullscreenVideoSize() override;
 
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 69582f9e..3c1b1908 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -4518,10 +4518,9 @@
 // frame and mouse up is on OOF iframe, the mouse up event is delivered to the
 // main frame as well to clear cached mouse states including autoscroll
 // selection state.
-// TODO(crbug.com/1512574): This test was detected flaky.
 IN_PROC_BROWSER_TEST_F(
     WebContentsImplBrowserTest,
-    DISABLED_MouseUpInOOPIframeShouldCancelMainFrameAutoscrollSelection) {
+    MouseUpInOOPIframeShouldCancelMainFrameAutoscrollSelection) {
   ASSERT_TRUE(embedded_test_server()->Start());
   WebContentsImpl* web_contents =
       static_cast<WebContentsImpl*>(shell()->web_contents());
@@ -4575,12 +4574,24 @@
                    ui::DomCode::US_A, ui::VKEY_A, false, false, false, false);
   SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('B'),
                    ui::DomCode::US_B, ui::VKEY_B, false, false, false, false);
-  RunUntilInputProcessed(web_contents->GetRenderWidgetHostWithPageFocus());
+  EXPECT_TRUE(ExecJs(web_contents,
+                     "var inputElement = document.getElementById('input1');"
+                     "new Promise(function(resolve) {"
+                     "  if (inputElement.value == 'AB')"
+                     "    resolve(true);"
+                     "  inputElement.addEventListener('change', () => {"
+                     "    if (inputElement.value == 'AB')"
+                     "      resolve(true);"
+                     "  });"
+                     "});"));
   EXPECT_EQ("AB",
             EvalJs(web_contents, "document.getElementById('input1').value")
                 .ExtractString());
 
   EXPECT_TRUE(ExecJs(web_contents,
+                     "document.addEventListener('mousedown', () => { "
+                     "window.receivedMouseDown = true; });"));
+  EXPECT_TRUE(ExecJs(web_contents,
                      "document.addEventListener('mouseup', () => { "
                      "window.receivedMouseUp = true; });"));
 
@@ -4605,7 +4616,10 @@
                      blink::WebMouseEvent::Button::kLeft,
                      gfx::Point(iframe_center_x, input_center_y));
   RunUntilInputProcessed(web_contents->GetRenderWidgetHostWithPageFocus());
-
+  EXPECT_TRUE(ExecJs(web_contents,
+                     "new Promise(resolve => setTimeout(() => {"
+                     "  resolve(window.receivedMouseDown);"
+                     "}));"));
   EXPECT_TRUE(web_contents->GetInputEventRouter()
                   ->root_view_receive_additional_mouse_up_);
 
@@ -4613,20 +4627,29 @@
                      blink::WebMouseEvent::Button::kLeft,
                      gfx::Point(iframe_center_x, input_center_y));
   RunUntilInputProcessed(web_contents->GetRenderWidgetHostWithPageFocus());
-
+  // Main frame should receive mouse up event.
+  EXPECT_TRUE(ExecJs(web_contents,
+                     "new Promise(resolve => setTimeout(() => {"
+                     "  resolve(window.receivedMouseUp);"
+                     "}));"));
   EXPECT_FALSE(web_contents->GetInputEventRouter()
                    ->root_view_receive_additional_mouse_up_);
 
-  // Main frame should receive mouse up event.
-  EXPECT_TRUE(EvalJs(web_contents, "window.receivedMouseUp").ExtractBool());
-
   // Type again in input element, insert text should be left to right.
   SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('E'),
                    ui::DomCode::US_E, ui::VKEY_E, false, false, false, false);
   SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('F'),
                    ui::DomCode::US_F, ui::VKEY_F, false, false, false, false);
-  RunUntilInputProcessed(web_contents->GetRenderWidgetHostWithPageFocus());
-
+  EXPECT_TRUE(ExecJs(web_contents,
+                     "var inputElement = document.getElementById('input1');"
+                     "new Promise(function(resolve) {"
+                     "  if (inputElement.value == 'EF')"
+                     "    resolve(true);"
+                     "  inputElement.addEventListener('change', () => {"
+                     "    if (inputElement.value == 'EF')"
+                     "      resolve(true);"
+                     "  });"
+                     "});"));
   EXPECT_EQ("EF",
             EvalJs(web_contents, "document.getElementById('input1').value")
                 .ExtractString());
diff --git a/content/ppapi_plugin/ppapi_blink_platform_impl.cc b/content/ppapi_plugin/ppapi_blink_platform_impl.cc
index 615e5b0..74c84d8 100644
--- a/content/ppapi_plugin/ppapi_blink_platform_impl.cc
+++ b/content/ppapi_plugin/ppapi_blink_platform_impl.cc
@@ -9,6 +9,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "base/notreached.h"
 #include "base/threading/platform_thread.h"
@@ -61,8 +62,8 @@
 #endif
 }
 
-uint64_t PpapiBlinkPlatformImpl::VisitedLinkHash(const char* canonical_url,
-                                                 size_t length) {
+uint64_t PpapiBlinkPlatformImpl::VisitedLinkHash(
+    std::string_view canonical_url) {
   NOTREACHED();
   return 0;
 }
diff --git a/content/ppapi_plugin/ppapi_blink_platform_impl.h b/content/ppapi_plugin/ppapi_blink_platform_impl.h
index c1edbed..aa30da40 100644
--- a/content/ppapi_plugin/ppapi_blink_platform_impl.h
+++ b/content/ppapi_plugin/ppapi_blink_platform_impl.h
@@ -8,6 +8,7 @@
 #include <stddef.h>
 
 #include <memory>
+#include <string_view>
 
 #include "build/build_config.h"
 #include "content/child/blink_platform_impl.h"
@@ -28,7 +29,7 @@
 
   // BlinkPlatformImpl methods:
   blink::WebSandboxSupport* GetSandboxSupport() override;
-  uint64_t VisitedLinkHash(const char* canonical_url, size_t length) override;
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override;
   bool IsLinkVisited(uint64_t link_hash) override;
   blink::WebString DefaultLocale() override;
 
diff --git a/content/public/browser/media_session.h b/content/public/browser/media_session.h
index e02511c..fca03cc5 100644
--- a/content/public/browser/media_session.h
+++ b/content/public/browser/media_session.h
@@ -30,6 +30,10 @@
   // none is currently available.
   CONTENT_EXPORT static MediaSession* Get(WebContents* contents);
 
+  // Returns the MediaSession associated to this WebContents if it already
+  // exists. Returns null otherwise.
+  CONTENT_EXPORT static MediaSession* GetIfExists(WebContents* contents);
+
   // Returns the source identity for the given BrowserContext.
   CONTENT_EXPORT static const base::UnguessableToken& GetSourceId(
       BrowserContext* browser_context);
diff --git a/content/public/browser/navigation_entry.h b/content/public/browser/navigation_entry.h
index a79b3fc..f37dab4e 100644
--- a/content/public/browser/navigation_entry.h
+++ b/content/public/browser/navigation_entry.h
@@ -110,6 +110,12 @@
   virtual void SetTitle(const std::u16string& title) = 0;
   virtual const std::u16string& GetTitle() = 0;
 
+  // The app title as set by the page. This will be empty if there is no app
+  // title set. This information is provided by an experimental meta tag. See:
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/DocumentSubtitle/explainer.md
+  virtual void SetAppTitle(const std::u16string& app_title) = 0;
+  virtual const std::u16string& GetAppTitle() = 0;
+
   // Page state is an opaque blob created by Blink that represents the state of
   // the page. This includes form entries and scroll position for each frame.
   // We store it so that we can supply it back to Blink to restore form state
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 0daa926..248b0111 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -639,6 +639,12 @@
   virtual void UpdateTitleForEntry(NavigationEntry* entry,
                                    const std::u16string& title) = 0;
 
+  // Returns app title of the current navigation entry. The apptitle is
+  // an alternative title text that can be used by app windows.
+  // See
+  // https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/DocumentSubtitle/explainer.md
+  virtual const std::u16string& GetAppTitle() = 0;
+
   // Returns the SiteInstance associated with the current page.
   virtual SiteInstance* GetSiteInstance() = 0;
 
diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h
index a3b5e3b..58c1d39f 100644
--- a/content/public/browser/web_contents_observer.h
+++ b/content/public/browser/web_contents_observer.h
@@ -57,6 +57,7 @@
 
 namespace content {
 
+class MediaSession;
 class NavigationEntry;
 class NavigationHandle;
 class RenderFrameHost;
@@ -814,6 +815,10 @@
   virtual void MediaMutedStatusChanged(const MediaPlayerId& id, bool muted) {}
   virtual void MediaDestroyed(const MediaPlayerId& id) {}
 
+  // Invoked when a MediaSession associated with this WebContents has been
+  // created and initialized.
+  virtual void MediaSessionCreated(MediaSession* media_session) {}
+
   // Invoked when the renderer process changes the page scale factor.
   virtual void OnPageScaleFactorChanged(float page_scale_factor) {}
 
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index ae07dcf..440ebd5 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -4,6 +4,8 @@
 
 #include "content/public/renderer/content_renderer_client.h"
 
+#include <string_view>
+
 #include "base/command_line.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
@@ -135,8 +137,8 @@
   return false;
 }
 
-uint64_t ContentRendererClient::VisitedLinkHash(const char* canonical_url,
-                                                size_t length) {
+uint64_t ContentRendererClient::VisitedLinkHash(
+    std::string_view canonical_url) {
   return 0;
 }
 
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index 72f72e2c..9cbf626 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -10,6 +10,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/files/file_path.h"
@@ -260,7 +261,7 @@
   virtual bool IsPrefetchOnly(RenderFrame* render_frame);
 
   // See blink::Platform.
-  virtual uint64_t VisitedLinkHash(const char* canonical_url, size_t length);
+  virtual uint64_t VisitedLinkHash(std::string_view canonical_url);
   virtual bool IsLinkVisited(uint64_t link_hash);
 
   // Creates a WebPrescientNetworking instance for |render_frame|. The returned
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 0557627..fa7c9cc 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <memory>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -279,9 +280,9 @@
       switches::kSingleProcess);
 }
 
-uint64_t RendererBlinkPlatformImpl::VisitedLinkHash(const char* canonical_url,
-                                                    size_t length) {
-  return GetContentClient()->renderer()->VisitedLinkHash(canonical_url, length);
+uint64_t RendererBlinkPlatformImpl::VisitedLinkHash(
+    std::string_view canonical_url) {
+  return GetContentClient()->renderer()->VisitedLinkHash(canonical_url);
 }
 
 bool RendererBlinkPlatformImpl::IsLinkVisited(uint64_t link_hash) {
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
index 9ff612d..5e505892 100644
--- a/content/renderer/renderer_blink_platform_impl.h
+++ b/content/renderer/renderer_blink_platform_impl.h
@@ -10,6 +10,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "base/containers/id_map.h"
 #include "base/memory/raw_ptr.h"
@@ -78,7 +79,7 @@
   // blink::Platform implementation.
   blink::WebSandboxSupport* GetSandboxSupport() override;
   virtual bool sandboxEnabled();
-  uint64_t VisitedLinkHash(const char* canonicalURL, size_t length) override;
+  uint64_t VisitedLinkHash(std::string_view canonical_url) override;
   bool IsLinkVisited(uint64_t linkHash) override;
   blink::WebString UserAgent() override;
   blink::UserAgentMetadata UserAgentMetadata() override;
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 1946d24..76ae288 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -2628,6 +2628,7 @@
 data/accessibility/form-controls/img-form.html
 data/accessibility/form-controls/img-map-pseudo.html
 data/accessibility/form-controls/map-inside-map.html
+data/accessibility/form-controls/progress-meter-crash.html
 data/accessibility/form-controls/role-group-expected-android.txt
 data/accessibility/form-controls/role-group-expected-blink.txt
 data/accessibility/form-controls/role-group.html
diff --git a/content/test/data/accessibility/form-controls/progress-meter-crash.html b/content/test/data/accessibility/form-controls/progress-meter-crash.html
new file mode 100644
index 0000000..0ed5c821
--- /dev/null
+++ b/content/test/data/accessibility/form-controls/progress-meter-crash.html
@@ -0,0 +1,10 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+  document.getElementById('div').setAttribute('aria-owns','bold')
+});
+</script>
+<progress>
+  <b id="bold">
+</progress>
+<div id='div'>
+
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 925d5f41e..38a56a94 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
@@ -973,6 +973,15 @@
 crbug.com/1448570 [ android android-pixel-2 angle-disabled no-passthrough ] conformance/extensions/webgl-compressed-texture-etc1.html [ Failure ]
 crbug.com/1448569 [ win angle-d3d11 ] conformance2/glsl3/texture-bias.html [ Failure ]
 
+#################################################################
+# Temporary suppressions for introduction of drawingBufferStorage
+#################################################################
+crbug.com/1230619 conformance/offscreencanvas/methods-worker.html [ Failure ]
+crbug.com/1230619 conformance/offscreencanvas/methods.html [ Failure ]
+crbug.com/1230619 conformance2/context/constants-and-properties-2.html [ Failure ]
+crbug.com/1230619 conformance2/offscreencanvas/methods-2-worker.html [ Failure ]
+crbug.com/1230619 conformance2/offscreencanvas/methods-2.html [ Failure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
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 18b1cc3..20a9d67 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
@@ -737,6 +737,13 @@
 crbug.com/1448743 [ android-nexus-5x ] conformance/textures/misc/texture-srgb-upload.html [ Failure ]
 crbug.com/1448743 [ angle-disabled chromeos chromeos-board-amd64-generic no-passthrough ] conformance/textures/misc/texture-srgb-upload.html [ Failure ]
 
+#################################################################
+# Temporary suppressions for introduction of drawingBufferStorage
+#################################################################
+crbug.com/1230619 conformance/context/constants-and-properties.html [ Failure ]
+crbug.com/1230619 conformance/offscreencanvas/methods-worker.html [ Failure ]
+crbug.com/1230619 conformance/offscreencanvas/methods.html [ Failure ]
+
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
 #######################################################################
diff --git a/device/bluetooth/BUILD.gn b/device/bluetooth/BUILD.gn
index 6a66287..e09b425d 100644
--- a/device/bluetooth/BUILD.gn
+++ b/device/bluetooth/BUILD.gn
@@ -67,6 +67,7 @@
     # Approved clients:
     "//chrome/browser/ash/nearby:*",
     "//chrome/browser/ui/webui/bluetooth_internals:*",
+    "//chrome/services/sharing/nearby/platform:*",
 
     # Implementation tests
     # Ideally only device_unittests, however android & fushia generate
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn
index b175ad2..9db79e2 100644
--- a/device/vr/BUILD.gn
+++ b/device/vr/BUILD.gn
@@ -153,6 +153,7 @@
     if (enable_openxr) {
       sources += [
         "openxr/context_provider_callbacks.h",
+        "openxr/exit_xr_present_reason.h",
         "openxr/openxr_anchor_manager.cc",
         "openxr/openxr_anchor_manager.h",
         "openxr/openxr_api_wrapper.cc",
@@ -204,8 +205,6 @@
         "openxr/openxr_view_configuration.cc",
         "openxr/openxr_view_configuration.h",
         "test/test_hook.h",
-        "windows/compositor_base.cc",
-        "windows/compositor_base.h",
       ]
 
       if (is_win) {
diff --git a/device/vr/openxr/exit_xr_present_reason.h b/device/vr/openxr/exit_xr_present_reason.h
new file mode 100644
index 0000000..d710dd90
--- /dev/null
+++ b/device/vr/openxr/exit_xr_present_reason.h
@@ -0,0 +1,21 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_OPENXR_EXIT_XR_PRESENT_REASON_H_
+#define DEVICE_VR_OPENXR_EXIT_XR_PRESENT_REASON_H_
+
+enum class ExitXrPresentReason : int32_t {
+  kUnknown = 0,
+  kMojoConnectionError = 1,
+  kOpenXrUninitialize = 2,
+  kStartRuntimeFailed = 3,
+  kOpenXrStartFailed = 4,
+  kXrEndFrameFailed = 5,
+  kGetFrameAfterSessionEnded = 6,
+  kSubmitFrameFailed = 7,
+  kBrowserShutdown = 8,
+  kXrPlatformHelperShutdown = 9,
+};
+
+#endif  // DEVICE_VR_OPENXR_EXIT_XR_PRESENT_REASON_H_
diff --git a/device/vr/openxr/openxr_api_wrapper.h b/device/vr/openxr/openxr_api_wrapper.h
index b911418c..b76c8411 100644
--- a/device/vr/openxr/openxr_api_wrapper.h
+++ b/device/vr/openxr/openxr_api_wrapper.h
@@ -11,6 +11,7 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
+#include "device/vr/openxr/exit_xr_present_reason.h"
 #include "device/vr/openxr/openxr_anchor_manager.h"
 #include "device/vr/openxr/openxr_graphics_binding.h"
 #include "device/vr/openxr/openxr_platform.h"
@@ -20,7 +21,6 @@
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/public/mojom/xr_session.mojom.h"
 #include "device/vr/vr_export.h"
-#include "device/vr/windows/compositor_base.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/openxr/src/include/openxr/openxr.h"
 
diff --git a/device/vr/openxr/openxr_device.cc b/device/vr/openxr/openxr_device.cc
index 721a3f9..897ff8cf2dd 100644
--- a/device/vr/openxr/openxr_device.cc
+++ b/device/vr/openxr/openxr_device.cc
@@ -164,7 +164,7 @@
 
     if (overlay_receiver_) {
       render_loop_->task_runner()->PostTask(
-          FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
+          FROM_HERE, base::BindOnce(&OpenXrRenderLoop::RequestOverlay,
                                     base::Unretained(render_loop_.get()),
                                     std::move(overlay_receiver_)));
     }
@@ -177,7 +177,7 @@
       &OpenXrDevice::OnVisibilityStateChanged, weak_ptr_factory_.GetWeakPtr());
 
   render_loop_->task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestSession,
+      FROM_HERE, base::BindOnce(&OpenXrRenderLoop::RequestSession,
                                 base::Unretained(render_loop_.get()),
                                 std::move(on_visibility_state_changed),
                                 std::move(options), std::move(my_callback)));
@@ -217,7 +217,7 @@
   if (render_loop_) {
     render_loop_->task_runner()->PostTask(
         FROM_HERE,
-        base::BindOnce(&XRCompositorCommon::ExitPresent,
+        base::BindOnce(&OpenXrRenderLoop::ExitPresent,
                        base::Unretained(render_loop_.get()), reason));
     render_loop_.reset();
   }
@@ -251,7 +251,7 @@
   // This should only be triggered if we have a session
   if (render_loop_) {
     render_loop_->task_runner()->PostTask(
-        FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
+        FROM_HERE, base::BindOnce(&OpenXrRenderLoop::RequestOverlay,
                                   base::Unretained(render_loop_.get()),
                                   std::move(overlay_receiver)));
   } else {
diff --git a/device/vr/openxr/openxr_device.h b/device/vr/openxr/openxr_device.h
index 3a1fb3e3..e210b2b 100644
--- a/device/vr/openxr/openxr_device.h
+++ b/device/vr/openxr/openxr_device.h
@@ -9,11 +9,11 @@
 
 #include "components/viz/common/gpu/context_provider.h"
 #include "device/vr/openxr/context_provider_callbacks.h"
+#include "device/vr/openxr/exit_xr_present_reason.h"
 #include "device/vr/openxr/openxr_platform_helper.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
 #include "device/vr/vr_device_base.h"
 #include "device/vr/vr_export.h"
-#include "device/vr/windows/compositor_base.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
diff --git a/device/vr/openxr/openxr_render_loop.cc b/device/vr/openxr/openxr_render_loop.cc
index afc361a..8cb76cc 100644
--- a/device/vr/openxr/openxr_render_loop.cc
+++ b/device/vr/openxr/openxr_render_loop.cc
@@ -5,10 +5,16 @@
 #include "device/vr/openxr/openxr_render_loop.h"
 
 #include "base/containers/contains.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/ranges/algorithm.h"
 #include "base/task/bind_post_task.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/trace_event/trace_event.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "build/build_config.h"
 #include "components/viz/common/gpu/context_provider.h"
+#include "device/vr/openxr/exit_xr_present_reason.h"
 #include "device/vr/openxr/openxr_api_wrapper.h"
 #include "device/vr/openxr/openxr_input_helper.h"
 #include "device/vr/util/stage_utils.h"
@@ -24,20 +30,52 @@
 
 #if BUILDFLAG(IS_WIN)
 #include <d3d11_4.h>
+
+#include "device/vr/windows/d3d11_texture_helper.h"
 #endif
 
+namespace {
+// Number of frames to use for sliding averages for pose timings,
+// as used for estimating prediction times.
+constexpr unsigned kSlidingAverageSize = 5;
+
+device::mojom::XRRenderInfoPtr GetRenderInfo(
+    const device::mojom::XRFrameData& frame_data) {
+  device::mojom::XRRenderInfoPtr result = device::mojom::XRRenderInfo::New();
+
+  result->frame_id = frame_data.frame_id;
+  result->mojo_from_viewer = frame_data.mojo_from_viewer.Clone();
+
+  for (size_t i = 0; i < frame_data.views.size(); i++) {
+    result->views.push_back(frame_data.views[i]->Clone());
+  }
+
+  return result;
+}
+
+}  // namespace
+
 namespace device {
 
+OpenXrRenderLoop::OutstandingFrame::OutstandingFrame() = default;
+OpenXrRenderLoop::OutstandingFrame::~OutstandingFrame() = default;
+
 OpenXrRenderLoop::OpenXrRenderLoop(
     VizContextProviderFactoryAsync context_provider_factory_async,
     XrInstance instance,
     const OpenXrExtensionHelper& extension_helper,
     OpenXrPlatformHelper* platform_helper)
-    : instance_(instance),
+    : XRThread("OpenXrRenderLoop"),
+      main_thread_task_runner_(
+          base::SingleThreadTaskRunner::GetCurrentDefault()),
+      instance_(instance),
       extension_helper_(extension_helper),
       context_provider_factory_async_(
           std::move(context_provider_factory_async)),
+      webxr_js_time_(kSlidingAverageSize),
+      webxr_gpu_time_(kSlidingAverageSize),
       platform_helper_(platform_helper) {
+  DCHECK(main_thread_task_runner_);
   DCHECK(instance_ != XR_NULL_HANDLE);
 }
 
@@ -45,11 +83,572 @@
   Stop();
 }
 
+void OpenXrRenderLoop::ExitPresent(ExitXrPresentReason reason) {
+  DVLOG(1) << __func__ << " reason=" << base::to_underlying(reason);
+  TRACE_EVENT_INSTANT1("xr", "ExitPresent", TRACE_EVENT_SCOPE_THREAD, "reason",
+                       base::to_underlying(reason));
+  if (!is_presenting_) {
+    return;
+  }
+
+  is_presenting_ = false;
+  webxr_has_pose_ = false;
+  presentation_receiver_.reset();
+  frame_data_receiver_.reset();
+  submit_client_.reset();
+
+  pending_frame_.reset();
+  delayed_get_frame_data_callback_.Reset();
+
+  // Reset webxr_visible_ for subsequent presentations.
+  webxr_visible_ = true;
+
+  // Kill outstanding overlays:
+  overlay_visible_ = false;
+  overlay_receiver_.reset();
+
+#if BUILDFLAG(IS_WIN)
+  texture_helper_.SetSourceAndOverlayVisible(false, false);
+#endif
+
+  // Don't call StopRuntime until this thread has finished the rest of the work.
+  // This is to prevent the OpenXrApiWrapper from being deleted before its
+  // cleanup work has finished.
+  task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&OpenXrRenderLoop::StopRuntime, base::Unretained(this)));
+}
+
+void OpenXrRenderLoop::GetFrameData(
+    mojom::XRFrameDataRequestOptionsPtr options,
+    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
+  TRACE_EVENT0("xr", "GetFrameData");
+  if (HasSessionEnded()) {
+    ExitPresent(ExitXrPresentReason::kGetFrameAfterSessionEnded);
+    return;
+  }
+
+  // HasSessionEnded() may do some work that alters the state of
+  // `is_presenting_`, in which case we'll get the ExitPresent log to know that
+  // we've ignored this request; but otherwise, we should log the rest of the
+  // state after that.
+  DVLOG(3) << __func__ << " is_presenting_=" << is_presenting_
+           << " webxr_visible=" << webxr_visible_
+           << " on_webxr_submitted_=" << !!on_webxr_submitted_
+           << " webxr_has_pose_=" << webxr_has_pose_;
+
+  if (!is_presenting_) {
+    return;
+  }
+
+  // If we've already given out a pose for the current frame, or aren't visible,
+  // delay giving out a pose until the next frame we are visible.
+  // However, if we aren't visible and the browser is waiting to learn that
+  // WebXR has submitted a frame, we can give out a pose as though we are
+  // visible.
+  if ((!webxr_visible_ && !on_webxr_submitted_) || webxr_has_pose_) {
+    // There should only be one outstanding GetFrameData call at a time.  We
+    // shouldn't get new ones until this resolves or presentation ends/restarts.
+    if (delayed_get_frame_data_callback_) {
+      frame_data_receiver_.ReportBadMessage(
+          "Multiple outstanding GetFrameData calls");
+      return;
+    }
+    delayed_get_frame_data_callback_ =
+        base::BindOnce(&OpenXrRenderLoop::GetFrameData, base::Unretained(this),
+                       std::move(options), std::move(callback));
+    return;
+  }
+
+  StartPendingFrame();
+  webxr_has_pose_ = true;
+  pending_frame_->webxr_has_pose_ = true;
+  pending_frame_->sent_frame_data_time_ = base::TimeTicks::Now();
+
+  // TODO(https://crbug.com/1218135): The lack of frame_data_ here indicates
+  // that we probably should have deferred this call, but it matches the
+  // behavior from before the stage parameters were updated in this function and
+  // avoids a crash. Likely the deferral above should check if we're awaiting
+  // either the webxr or overlay submit.
+  if (pending_frame_->frame_data_) {
+    // If the stage parameters have been updated since the last frame that was
+    // sent, send the updated values.
+    pending_frame_->frame_data_->stage_parameters_id = stage_parameters_id_;
+    if (options->stage_parameters_id != stage_parameters_id_) {
+      pending_frame_->frame_data_->stage_parameters =
+          current_stage_parameters_.Clone();
+    }
+  } else {
+    TRACE_EVENT0("xr", "GetFrameData Missing FrameData");
+  }
+
+  // Yield here to let the event queue process pending mojo messages,
+  // specifically the next gamepad callback request that's likely to
+  // have been sent during WaitGetPoses.
+  task_runner()->PostTask(
+      FROM_HERE, base::BindOnce(&OpenXrRenderLoop::SendFrameData,
+                                base::Unretained(this), std::move(callback),
+                                std::move(pending_frame_->frame_data_)));
+
+  next_frame_id_ += 1;
+  if (next_frame_id_ < 0) {
+    next_frame_id_ = 0;
+  }
+}
+
+void OpenXrRenderLoop::RequestOverlay(
+    mojo::PendingReceiver<mojom::ImmersiveOverlay> receiver) {
+  overlay_receiver_.reset();
+  overlay_receiver_.Bind(std::move(receiver));
+
+  // WebXR is visible and overlay hidden by default until the overlay overrides
+  // this.
+  SetOverlayAndWebXRVisibility(false, true);
+}
+
+void OpenXrRenderLoop::RequestSession(
+    base::RepeatingCallback<void(mojom::XRVisibilityState)>
+        on_visibility_state_changed,
+    mojom::XRRuntimeSessionOptionsPtr options,
+    RequestSessionCallback callback) {
+  webxr_has_pose_ = false;
+  presentation_receiver_.reset();
+  frame_data_receiver_.reset();
+
+  EnableSupportedFeatures(options->required_features,
+                          options->optional_features);
+
+  // Call OpenXrRenderLoop::StartRuntime. Upon completion, this function will
+  // invoke the provided start_runtime_callback.
+  // OpenXrRenderLoop::StartRuntimeFinish. We setup BindOnce such that all of
+  // the parameters give to us here in OpenXrRenderLoop::RequestSession are
+  // passed through to StartRuntimeFinish so that it can finish the job.
+  StartRuntime(base::BindOnce(&OpenXrRenderLoop::StartRuntimeFinish,
+                              base::Unretained(this),
+                              std::move(on_visibility_state_changed),
+                              std::move(options), std::move(callback)));
+}
+
 bool OpenXrRenderLoop::IsFeatureEnabled(
     device::mojom::XRSessionFeature feature) const {
   return base::Contains(enabled_features_, feature);
 }
 
+void OpenXrRenderLoop::SetVisibilityState(
+    mojom::XRVisibilityState visibility_state) {
+  if (visibility_state_ != visibility_state) {
+    visibility_state_ = visibility_state;
+    if (on_visibility_state_changed_) {
+      main_thread_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(on_visibility_state_changed_, visibility_state));
+    }
+  }
+}
+
+void OpenXrRenderLoop::SetStageParameters(
+    mojom::VRStageParametersPtr stage_parameters) {
+  // If the stage parameters are identical no need to update them.
+  if ((!current_stage_parameters_ && !stage_parameters) ||
+      (current_stage_parameters_ && stage_parameters &&
+       current_stage_parameters_.Equals(stage_parameters))) {
+    return;
+  }
+
+  // If they have changed, increment the ID and save the new parameters.
+  stage_parameters_id_++;
+  current_stage_parameters_ = std::move(stage_parameters);
+}
+
+#if BUILDFLAG(IS_WIN)
+void OpenXrRenderLoop::SubmitFrameWithTextureHandle(
+    int16_t frame_index,
+    mojo::PlatformHandle texture_handle,
+    const gpu::SyncToken& sync_token) {
+  DVLOG(3) << __func__ << " frame_index=" << frame_index;
+  TRACE_EVENT1("xr", "SubmitFrameWithTextureHandle", "frameIndex", frame_index);
+  if (!MarkFrameSubmitted(frame_index)) {
+    return;
+  }
+
+  base::win::ScopedHandle scoped_handle = texture_handle.is_valid()
+                                              ? texture_handle.TakeHandle()
+                                              : base::win::ScopedHandle();
+  texture_helper_.SetSourceTexture(std::move(scoped_handle), sync_token,
+                                   left_webxr_bounds_, right_webxr_bounds_);
+
+  // Regardless of success - try to composite what we have.
+  MaybeCompositeAndSubmit();
+}
+#endif
+
+void OpenXrRenderLoop::CleanUp() {
+  DVLOG(1) << __func__;
+  submit_client_.reset();
+  webxr_has_pose_ = false;
+  presentation_receiver_.reset();
+  frame_data_receiver_.reset();
+  overlay_receiver_.reset();
+  StopRuntime();
+}
+
+void OpenXrRenderLoop::ClearPendingFrame() {
+  // Complete the frame if OpenXR has started one with BeginFrame. This also
+  // releases the swapchain image that was acquired in BeginFrame so that the
+  // next frame can acquire it.
+  if (openxr_->HasPendingFrame() && XR_FAILED(openxr_->EndFrame())) {
+    // The start of the next frame will detect that the session has ended via
+    // HasSessionEnded and will exit presentation.
+    ExitPresent(ExitXrPresentReason::kXrEndFrameFailed);
+    return;
+  }
+
+  pending_frame_.reset();
+  // Send frame data to outstanding requests.
+  if (delayed_get_frame_data_callback_ &&
+      (webxr_visible_ || on_webxr_submitted_)) {
+    // If WebXR is not visible, but the browser wants to know when it submits a
+    // frame, we allow the renderer to receive poses.
+    std::move(delayed_get_frame_data_callback_).Run();
+  }
+}
+
+void OpenXrRenderLoop::StartPendingFrame() {
+  DVLOG(3) << __func__ << " pending_frame_=" << pending_frame_.has_value();
+  if (!pending_frame_) {
+    pending_frame_.emplace();
+    pending_frame_->waiting_for_webxr_ = webxr_visible_;
+    pending_frame_->waiting_for_overlay_ = overlay_visible_;
+    pending_frame_->frame_data_ = GetNextFrameData();
+    // GetNextFrameData() should never return null:
+    DCHECK(pending_frame_->frame_data_);
+    pending_frame_->render_info_ = GetRenderInfo(*pending_frame_->frame_data_);
+  }
+}
+
+void OpenXrRenderLoop::StartRuntimeFinish(
+    base::RepeatingCallback<void(mojom::XRVisibilityState)>
+        on_visibility_state_changed,
+    mojom::XRRuntimeSessionOptionsPtr options,
+    RequestSessionCallback callback,
+    bool success) {
+  if (!success) {
+    TRACE_EVENT_INSTANT0("xr", "Failed to start runtime",
+                         TRACE_EVENT_SCOPE_THREAD);
+    main_thread_task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(std::move(callback), false, nullptr));
+    return;
+  }
+
+  on_visibility_state_changed_ = std::move(on_visibility_state_changed);
+
+  // Queue up a notification to the requester of the current visibility state,
+  // so that it can be initialized to the right value.
+  if (on_visibility_state_changed_) {
+    main_thread_task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(on_visibility_state_changed_, visibility_state_));
+  }
+
+  device::mojom::XRPresentationTransportOptionsPtr transport_options =
+      device::mojom::XRPresentationTransportOptions::New();
+
+  if (graphics_binding_->IsUsingSharedImages()) {
+    transport_options->transport_method =
+        device::mojom::XRPresentationTransportMethod::DRAW_INTO_TEXTURE_MAILBOX;
+  } else if constexpr (BUILDFLAG(IS_WIN)) {
+    transport_options->transport_method =
+        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEXTURE_HANDLE;
+  } else {
+    transport_options->transport_method =
+        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_MAILBOX_HOLDER;
+  }
+
+  // Only set boolean options that we need. Default is false, and we should be
+  // able to safely ignore ones that our implementation doesn't care about.
+  transport_options->wait_for_transfer_notification = true;
+
+  LogViewerType(VrViewerType::OPENXR_UNKNOWN);
+
+  auto submit_frame_sink = device::mojom::XRPresentationConnection::New();
+  submit_frame_sink->provider =
+      presentation_receiver_.BindNewPipeAndPassRemote();
+  submit_frame_sink->client_receiver =
+      submit_client_.BindNewPipeAndPassReceiver();
+  submit_frame_sink->transport_options = std::move(transport_options);
+
+  auto session = device::mojom::XRSession::New();
+  session->data_provider = frame_data_receiver_.BindNewPipeAndPassRemote();
+  session->submit_frame_sink = std::move(submit_frame_sink);
+
+  session->enabled_features.insert(session->enabled_features.end(),
+                                   enabled_features_.begin(),
+                                   enabled_features_.end());
+
+  session->device_config = device::mojom::XRSessionDeviceConfig::New();
+  session->device_config->enable_anti_aliasing =
+      openxr_->CanEnableAntiAliasing();
+  session->device_config->views = openxr_->GetDefaultViews();
+  session->enviroment_blend_mode =
+      openxr_->PickEnvironmentBlendModeForSession(options->mode);
+  session->interaction_mode = device::mojom::XRInteractionMode::kWorldSpace;
+
+  main_thread_task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), true, std::move(session)));
+  is_presenting_ = true;
+
+#if BUILDFLAG(IS_WIN)
+  texture_helper_.SetSourceAndOverlayVisible(webxr_visible_, overlay_visible_);
+#endif
+}
+
+void OpenXrRenderLoop::MaybeCompositeAndSubmit() {
+  DVLOG(3) << __func__;
+  if (!pending_frame_) {
+    // There is no outstanding frame, nor frame to composite, but there may be
+    // pending GetFrameData calls, so ClearPendingFrame() to respond to them.
+    ClearPendingFrame();
+    return;
+  }
+
+  // Check if we have obtained all layers (overlay and webxr) that we need.
+  if (pending_frame_->waiting_for_webxr_ ||
+      pending_frame_->waiting_for_overlay_) {
+    DVLOG(3) << __func__ << "Waiting for additional layers, waiting_for_webxr_="
+             << pending_frame_->waiting_for_webxr_
+             << " waiting_for_overlay=" << pending_frame_->waiting_for_overlay_;
+    // Haven't received submits from all layers.
+    return;
+  }
+
+  // TODO(https://crbug.com/1454950): Unify OpenXr Rendering paths.
+#if BUILDFLAG(IS_WIN)
+  bool copy_successful = false;
+  bool has_webxr_content = pending_frame_->webxr_submitted_ && webxr_visible_;
+  bool has_overlay_content =
+      pending_frame_->overlay_submitted_ && overlay_visible_;
+  bool can_submit = has_webxr_content || has_overlay_content;
+
+  // Tell texture helper to composite, then grab the output texture, and submit.
+  // If we submitted, set up the next frame, and send outstanding pose requests.
+  if (can_submit) {
+    copy_successful = texture_helper_.UpdateBackbufferSizes() &&
+                      texture_helper_.CompositeToBackBuffer();
+  } else {
+    texture_helper_.CleanupNoSubmit();
+  }
+#elif BUILDFLAG(IS_ANDROID)
+  bool copy_successful = true;
+#endif
+
+  // A copy can only be successful if we actually tried to submit.
+  if (copy_successful) {
+    pending_frame_->frame_ready_time_ = base::TimeTicks::Now();
+    if (!SubmitCompositedFrame()) {
+      ExitPresent(ExitXrPresentReason::kSubmitFrameFailed);
+      // ExitPresent() clears pending_frame_, so return here to avoid
+      // accessing it below.
+      return;
+    }
+  }
+
+  if (pending_frame_->webxr_submitted_ && copy_successful) {
+    // We've submitted a frame.
+    webxr_js_time_.AddSample(pending_frame_->submit_frame_time_ -
+                             pending_frame_->sent_frame_data_time_);
+    webxr_gpu_time_.AddSample(pending_frame_->frame_ready_time_ -
+                              pending_frame_->submit_frame_time_);
+
+    TRACE_EVENT_INSTANT2(
+        "gpu", "WebXR frame time (ms)", TRACE_EVENT_SCOPE_THREAD, "javascript",
+        webxr_js_time_.GetAverage().InMillisecondsF(), "rendering",
+        webxr_gpu_time_.GetAverage().InMillisecondsF());
+    fps_meter_.AddFrame(base::TimeTicks::Now());
+    TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS());
+  }
+
+  if (pending_frame_->webxr_submitted_ && submit_client_) {
+    // Tell WebVR that we are done with the texture (if we got a texture)
+    submit_client_->OnSubmitFrameTransferred(copy_successful);
+    submit_client_->OnSubmitFrameRendered();
+    TRACE_EVENT1("xr", "SubmitFrameTransferred", "success", copy_successful);
+  }
+
+  if (pending_frame_->overlay_submitted_ && overlay_submit_callback_) {
+    // Tell the browser/overlay that we are done with its texture so it can be
+    // reused.
+    std::move(overlay_submit_callback_).Run(copy_successful);
+  }
+
+  ClearPendingFrame();
+}
+
+bool OpenXrRenderLoop::MarkFrameSubmitted(int16_t frame_index) {
+  DVLOG(3) << __func__;
+  webxr_has_pose_ = false;
+  // Tell the browser that WebXR has submitted a frame.
+  if (on_webxr_submitted_) {
+    std::move(on_webxr_submitted_).Run();
+  }
+
+  if (!pending_frame_ ||
+      pending_frame_->render_info_->frame_id != frame_index) {
+    // We weren't expecting a submitted frame.  This can happen if WebXR was
+    // hidden by an overlay for some time.
+    if (submit_client_) {
+      submit_client_->OnSubmitFrameTransferred(false);
+      submit_client_->OnSubmitFrameRendered();
+      TRACE_EVENT1("xr", "SubmitFrameTransferred", "success", false);
+    }
+    return false;
+  }
+
+  pending_frame_->waiting_for_webxr_ = false;
+  pending_frame_->webxr_submitted_ = true;
+  pending_frame_->submit_frame_time_ = base::TimeTicks::Now();
+
+  return true;
+}
+
+void OpenXrRenderLoop::SubmitFrameMissing(int16_t frame_index,
+                                          const gpu::SyncToken& sync_token) {
+  DVLOG(3) << __func__ << " frame_index=" << frame_index;
+  TRACE_EVENT_INSTANT0("xr", "SubmitFrameMissing", TRACE_EVENT_SCOPE_THREAD);
+  if (pending_frame_) {
+    // WebXR for this frame is hidden.
+    pending_frame_->waiting_for_webxr_ = false;
+  }
+  webxr_has_pose_ = false;
+#if (BUILDFLAG(IS_ANDROID))
+  // We haven't finished the rendering path on Android yet so are just calling
+  // this to ensure that the page stays un-stuck while we aren't actually
+  // rendering anything.
+  // TODO(alcooper): Clean this up.
+  pending_frame_->webxr_submitted_ = true;
+#endif
+  MaybeCompositeAndSubmit();
+}
+
+void OpenXrRenderLoop::UpdateLayerBounds(int16_t frame_id,
+                                         const gfx::RectF& left_bounds,
+                                         const gfx::RectF& right_bounds,
+                                         const gfx::Size& source_size) {
+  // Bounds are updated instantly, rather than waiting for frame_id.  This works
+  // since blink always passes the current frame_id when updating the bounds.
+  // Ignoring the frame_id keeps the logic simpler, so this can more easily
+  // merge with vr_shell_gl eventually.
+  left_webxr_bounds_ = left_bounds;
+  right_webxr_bounds_ = right_bounds;
+
+  // Swap top/bottom to account for differences between D3D and GL coordinates.
+  left_webxr_bounds_.set_y(
+      1 - (left_webxr_bounds_.y() + left_webxr_bounds_.height()));
+  right_webxr_bounds_.set_y(
+      1 - (right_webxr_bounds_.y() + right_webxr_bounds_.height()));
+
+  source_size_ = source_size;
+
+  graphics_binding_->SetTransferSize(source_size);
+}
+
+void OpenXrRenderLoop::SubmitOverlayTexture(
+    int16_t frame_id,
+    mojo::PlatformHandle texture_handle,
+    const gpu::SyncToken& sync_token,
+    const gfx::RectF& left_bounds,
+    const gfx::RectF& right_bounds,
+    SubmitOverlayTextureCallback overlay_submit_callback) {
+  TRACE_EVENT_INSTANT0("xr", "SubmitOverlay", TRACE_EVENT_SCOPE_THREAD);
+  DCHECK(overlay_visible_);
+  overlay_submit_callback_ = std::move(overlay_submit_callback);
+  if (!pending_frame_) {
+    // We may stop presenting while there is a pending SubmitOverlayTexture
+    // outstanding.  If we get an overlay submit we weren't expecting, just
+    // ignore it.
+    DCHECK(!is_presenting_);
+    std::move(overlay_submit_callback_).Run(false);
+    return;
+  }
+
+  pending_frame_->waiting_for_overlay_ = false;
+
+#if BUILDFLAG(IS_WIN)
+  texture_helper_.SetOverlayTexture(texture_handle.TakeHandle(), sync_token,
+                                    left_bounds, right_bounds);
+  pending_frame_->overlay_submitted_ = true;
+
+  // Regardless of success - try to composite what we have.
+  MaybeCompositeAndSubmit();
+#endif
+}
+
+void OpenXrRenderLoop::RequestNextOverlayPose(
+    RequestNextOverlayPoseCallback callback) {
+  DVLOG(3) << __func__;
+  // We will only request poses while the overlay is visible.
+  DCHECK(overlay_visible_);
+  TRACE_EVENT_INSTANT0("xr", "RequestOverlayPose", TRACE_EVENT_SCOPE_THREAD);
+
+  // Ensure we have a pending frame.
+  StartPendingFrame();
+  pending_frame_->overlay_has_pose_ = true;
+  std::move(callback).Run(pending_frame_->render_info_->Clone());
+}
+
+void OpenXrRenderLoop::SetOverlayAndWebXRVisibility(bool overlay_visible,
+                                                    bool webxr_visible) {
+  DVLOG(1) << __func__ << " overlay_visible=" << overlay_visible
+           << " webxr_visible=" << webxr_visible;
+  TRACE_EVENT_INSTANT2("xr", "SetOverlayAndWebXRVisibility",
+                       TRACE_EVENT_SCOPE_THREAD, "overlay", overlay_visible,
+                       "webxr", webxr_visible);
+  // Update state.
+  webxr_visible_ = webxr_visible;
+  overlay_visible_ = overlay_visible;
+  if (pending_frame_) {
+    pending_frame_->waiting_for_webxr_ =
+        pending_frame_->waiting_for_webxr_ && webxr_visible;
+    pending_frame_->waiting_for_overlay_ =
+        pending_frame_->waiting_for_overlay_ && overlay_visible;
+  }
+
+  // Update texture helper.
+#if BUILDFLAG(IS_WIN)
+  texture_helper_.SetSourceAndOverlayVisible(webxr_visible, overlay_visible);
+#endif
+
+  // Maybe composite and submit if we have a pending that is now valid to
+  // submit.
+  MaybeCompositeAndSubmit();
+}
+
+void OpenXrRenderLoop::RequestNotificationOnWebXrSubmitted(
+    RequestNotificationOnWebXrSubmittedCallback callback) {
+  on_webxr_submitted_ = std::move(callback);
+}
+
+void OpenXrRenderLoop::SendFrameData(
+    XRFrameDataProvider::GetFrameDataCallback callback,
+    mojom::XRFrameDataPtr frame_data) {
+  DVLOG(3) << __func__;
+  TRACE_EVENT0("xr", "SendFrameData");
+
+  // This method represents a call from the renderer process. If our visibility
+  // state is hidden, we should avoid handing "sensitive" information, like the
+  // pose back up to the renderer. Note that this check is done here as other
+  // methods (RequestNextOverlayPose) represent a call from the browser process,
+  // which should receive the pose.
+  bool is_visible =
+      (visibility_state_ != device::mojom::XRVisibilityState::HIDDEN);
+
+  // We have posted a message to allow other calls to get through, and now state
+  // may have changed.  WebXR may not be presenting any more, or may be hidden.
+  std::move(callback).Run(is_presenting_ && is_visible &&
+                                  (webxr_visible_ || on_webxr_submitted_)
+                              ? std::move(frame_data)
+                              : mojom::XRFrameData::New());
+}
+
 mojom::XRFrameDataPtr OpenXrRenderLoop::GetNextFrameData() {
   DVLOG(3) << __func__;
   mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
@@ -84,7 +683,7 @@
 
       if (anchor_manager) {
         frame_data->anchors_data = anchor_manager->ProcessAnchorsForFrame(
-            openxr_.get(), GetCurrentStageParameters(),
+            openxr_.get(), current_stage_parameters_,
             frame_data->input_state.value(),
             openxr_->GetPredictedDisplayTime());
       }
@@ -110,7 +709,7 @@
   return frame_data;
 }
 
-// StartRuntime is called by XRCompositorCommon::RequestSession. When the
+// StartRuntime is called by OpenXrRenderLoop::RequestSession. When the
 // runtime is fully started, start_runtime_callback.Run must be called with a
 // success boolean, or false on failure. OpenXrRenderLoop::StartRuntime waits
 // until the Viz context provider is fully started before running
@@ -239,32 +838,6 @@
       });
 }
 
-device::mojom::XREnvironmentBlendMode OpenXrRenderLoop::GetEnvironmentBlendMode(
-    device::mojom::XRSessionMode session_mode) {
-  return openxr_->PickEnvironmentBlendModeForSession(session_mode);
-}
-
-device::mojom::XRInteractionMode OpenXrRenderLoop::GetInteractionMode(
-    device::mojom::XRSessionMode session_mode) {
-  return device::mojom::XRInteractionMode::kWorldSpace;
-}
-
-bool OpenXrRenderLoop::CanEnableAntiAliasing() const {
-  return openxr_->CanEnableAntiAliasing();
-}
-
-std::vector<mojom::XRViewPtr> OpenXrRenderLoop::GetDefaultViews() const {
-  return openxr_->GetDefaultViews();
-}
-
-void OpenXrRenderLoop::OnLayerBoundsChanged(const gfx::Size& source_size) {
-  graphics_binding_->SetTransferSize(source_size);
-}
-
-void OpenXrRenderLoop::OnSessionStart() {
-  LogViewerType(VrViewerType::OPENXR_UNKNOWN);
-}
-
 bool OpenXrRenderLoop::HasSessionEnded() {
   return openxr_ && openxr_->UpdateAndGetSessionEnded();
 }
@@ -273,27 +846,11 @@
   return XR_SUCCEEDED(openxr_->EndFrame());
 }
 
-void OpenXrRenderLoop::ClearPendingFrameInternal() {
-  // Complete the frame if OpenXR has started one with BeginFrame. This also
-  // releases the swapchain image that was acquired in BeginFrame so that the
-  // next frame can acquire it.
-  if (openxr_->HasPendingFrame() && XR_FAILED(openxr_->EndFrame())) {
-    // The start of the next frame will detect that the session has ended via
-    // HasSessionEnded and will exit presentation.
-    ExitPresent(ExitXrPresentReason::kXrEndFrameFailed);
-    return;
-  }
-}
-
-bool OpenXrRenderLoop::IsUsingSharedImages() const {
-  return graphics_binding_->IsUsingSharedImages();
-}
-
 void OpenXrRenderLoop::SubmitFrame(int16_t frame_index,
                                    const gpu::MailboxHolder& mailbox,
                                    base::TimeDelta time_waited) {
   DVLOG(3) << __func__ << " frame_index=" << frame_index;
-  CHECK(!IsUsingSharedImages());
+  CHECK(!graphics_binding_->IsUsingSharedImages());
   DCHECK(BUILDFLAG(IS_ANDROID));
   // TODO(https://crbug.com/1454942): Support non-shared buffer mode.
   SubmitFrameMissing(frame_index, mailbox.sync_token);
@@ -338,8 +895,7 @@
 #endif
 
   // Calling SubmitFrameWithTextureHandle can cause openxr_ and
-  // context_provider_ to become nullptr in ClearPendingFrameInternal if we
-  // decide to stop the runtime.
+  // context_provider_ to become nullptr if we decide to stop the runtime.
   if (context_provider_) {
     gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL();
     gl->DestroyGpuFenceCHROMIUM(id);
diff --git a/device/vr/openxr/openxr_render_loop.h b/device/vr/openxr/openxr_render_loop.h
index 697fdda9..b77b0158 100644
--- a/device/vr/openxr/openxr_render_loop.h
+++ b/device/vr/openxr/openxr_render_loop.h
@@ -10,12 +10,22 @@
 
 #include "base/functional/callback.h"
 #include "base/memory/raw_ref.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
 #include "components/viz/common/gpu/context_lost_observer.h"
 #include "device/vr/openxr/context_provider_callbacks.h"
+#include "device/vr/openxr/exit_xr_present_reason.h"
 #include "device/vr/openxr/openxr_anchor_manager.h"
 #include "device/vr/openxr/openxr_graphics_binding.h"
 #include "device/vr/openxr/openxr_platform_helper.h"
-#include "device/vr/windows/compositor_base.h"
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
+#include "device/vr/public/mojom/vr_service.mojom.h"
+#include "device/vr/public/mojom/xr_session.mojom.h"
+#include "device/vr/util/fps_meter.h"
+#include "device/vr/util/sliding_average.h"
+#include "device/vr/vr_device.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -26,6 +36,20 @@
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/platform/platform_handle.h"
 #include "third_party/openxr/src/include/openxr/openxr.h"
+#include "ui/gfx/geometry/rect_f.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/threading/thread.h"
+#include "device/vr/windows/d3d11_texture_helper.h"
+#endif
+
+#if BUILDFLAG(IS_ANDROID)
+#include "base/android/java_handler_thread.h"
+#endif
+
+namespace gpu::gles2 {
+class GLES2Interface;
+}  // namespace gpu::gles2
 
 namespace gfx {
 class GpuFence;
@@ -35,10 +59,33 @@
 
 class OpenXrApiWrapper;
 
-class OpenXrRenderLoop : public XRCompositorCommon,
+#if BUILDFLAG(IS_ANDROID)
+class XRThread : public base::android::JavaHandlerThread {
+ public:
+  explicit XRThread(const char* name)
+      : base::android::JavaHandlerThread(name) {}
+  ~XRThread() override = default;
+};
+#elif BUILDFLAG(IS_WIN)
+class XRThread : public base::Thread {
+ public:
+  explicit XRThread(const char* name) : base::Thread(name) {}
+  ~XRThread() override = default;
+};
+#else
+#error "Trying to build OpenXR for an unsupported platform"
+#endif
+
+class OpenXrRenderLoop : public XRThread,
+                         public mojom::XRPresentationProvider,
+                         public mojom::XRFrameDataProvider,
+                         public mojom::ImmersiveOverlay,
                          public mojom::XREnvironmentIntegrationProvider,
                          public viz::ContextLostObserver {
  public:
+  using RequestSessionCallback =
+      base::OnceCallback<void(bool result, mojom::XRSessionPtr)>;
+
   OpenXrRenderLoop(
       VizContextProviderFactoryAsync context_provider_factory_async,
       XrInstance instance,
@@ -50,36 +97,117 @@
 
   ~OpenXrRenderLoop() override;
 
+  void ExitPresent(ExitXrPresentReason reason);
+
+  gpu::gles2::GLES2Interface* GetContextGL();
+
+  void GetFrameData(
+      mojom::XRFrameDataRequestOptionsPtr options,
+      XRFrameDataProvider::GetFrameDataCallback callback) override;
+
+  void RequestOverlay(mojo::PendingReceiver<mojom::ImmersiveOverlay> receiver);
+
+  void RequestSession(base::RepeatingCallback<void(mojom::XRVisibilityState)>
+                          on_visibility_state_changed,
+                      mojom::XRRuntimeSessionOptionsPtr options,
+                      RequestSessionCallback callback);
+
  private:
-  // XRCompositorCommon:
-  gpu::gles2::GLES2Interface* GetContextGL() override;
-  void ClearPendingFrameInternal() override;
-  bool IsUsingSharedImages() const override;
+  void SetVisibilityState(mojom::XRVisibilityState visibility_state);
+  void SetStageParameters(mojom::VRStageParametersPtr stage_parameters);
+
+  // base::Thread overrides:
+  void CleanUp() override;
+
+  void ClearPendingFrame();
+  void StartPendingFrame();
+
+  void StartRuntimeFinish(
+      base::RepeatingCallback<void(mojom::XRVisibilityState)>
+          on_visibility_state_changed,
+      mojom::XRRuntimeSessionOptionsPtr options,
+      RequestSessionCallback callback,
+      bool success);
+
+  // Will Submit if we have textures submitted from the Overlay (if it is
+  // visible), and WebXR (if it is visible).  We decide what to wait for during
+  // StartPendingFrame, may mark things as ready after SubmitFrameMissing and
+  // SubmitFrameWithTextureHandle (for WebXR), or SubmitOverlayTexture (for
+  // overlays), or SetOverlayAndWebXRVisibility (for WebXR and overlays).
+  // Finally, if we exit presentation while waiting for outstanding submits, we
+  // will clean up our pending-frame state.
+  void MaybeCompositeAndSubmit();
+
+  // Sets all relevant internal state to mark that we have successfully received
+  // a frame. Will return whether or not the given frame index was expected.
+  // If not expected, not all state may be successfully cleared.
+  bool MarkFrameSubmitted(int16_t frame_index);
+
+  // XRPresentationProvider overrides:
+#if BUILDFLAG(IS_WIN)
+  void SubmitFrameWithTextureHandle(int16_t frame_index,
+                                    mojo::PlatformHandle texture_handle,
+                                    const gpu::SyncToken& sync_token) override;
+#endif
+  void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
   void SubmitFrame(int16_t frame_index,
                    const gpu::MailboxHolder& mailbox,
-                   base::TimeDelta time_waited) override;
+                   base::TimeDelta time_waited) final;
   void SubmitFrameDrawnIntoTexture(int16_t frame_index,
                                    const gpu::SyncToken&,
                                    base::TimeDelta time_waited) override;
+  void UpdateLayerBounds(int16_t frame_id,
+                         const gfx::RectF& left_bounds,
+                         const gfx::RectF& right_bounds,
+                         const gfx::Size& source_size) override;
 
-  // XRDeviceAbstraction:
-  mojom::XRFrameDataPtr GetNextFrameData() override;
-  void StartRuntime(StartRuntimeCallback start_runtime_callback) override;
-  void StopRuntime() override;
-  void OnSessionStart() override;
-  bool HasSessionEnded() override;
-  bool SubmitCompositedFrame() override;
+  // ImmersiveOverlay:
+  void SubmitOverlayTexture(int16_t frame_id,
+                            mojo::PlatformHandle texture,
+                            const gpu::SyncToken& sync_token,
+                            const gfx::RectF& left_bounds,
+                            const gfx::RectF& right_bounds,
+                            SubmitOverlayTextureCallback callback) override;
+  void RequestNextOverlayPose(RequestNextOverlayPoseCallback callback) override;
+  void SetOverlayAndWebXRVisibility(bool overlay_visible,
+                                    bool webxr_visible) override;
+  void RequestNotificationOnWebXrSubmitted(
+      RequestNotificationOnWebXrSubmittedCallback callback) override;
+
+  void SendFrameData(XRFrameDataProvider::GetFrameDataCallback callback,
+                     mojom::XRFrameDataPtr frame_data);
+
+  struct OutstandingFrame {
+    OutstandingFrame();
+    ~OutstandingFrame();
+    bool webxr_has_pose_ = false;
+    bool overlay_has_pose_ = false;
+    bool webxr_submitted_ = false;
+    bool overlay_submitted_ = false;
+    bool waiting_for_webxr_ = false;
+    bool waiting_for_overlay_ = false;
+
+    mojom::XRFrameDataPtr frame_data_;
+    mojom::XRRenderInfoPtr render_info_;
+
+    base::TimeTicks sent_frame_data_time_;
+    base::TimeTicks submit_frame_time_;
+    base::TimeTicks frame_ready_time_;
+  };
+
+  mojom::XRFrameDataPtr GetNextFrameData();
+
+  // TODO(https://crbug.com/1516973): Investigate removing this callback.
+  using StartRuntimeCallback = base::OnceCallback<void(bool success)>;
+
+  void StartRuntime(StartRuntimeCallback start_runtime_callback);
+  void StopRuntime();
+  void OnSessionStart();
+  bool HasSessionEnded();
+  bool SubmitCompositedFrame();
   void EnableSupportedFeatures(
       const std::vector<device::mojom::XRSessionFeature>& requiredFeatures,
-      const std::vector<device::mojom::XRSessionFeature>& optionalFeatures)
-      override;
-  device::mojom::XREnvironmentBlendMode GetEnvironmentBlendMode(
-      device::mojom::XRSessionMode session_mode) override;
-  device::mojom::XRInteractionMode GetInteractionMode(
-      device::mojom::XRSessionMode session_mode) override;
-  bool CanEnableAntiAliasing() const override;
-  std::vector<mojom::XRViewPtr> GetDefaultViews() const override;
-  void OnLayerBoundsChanged(const gfx::Size& source_size) override;
+      const std::vector<device::mojom::XRSessionFeature>& optionalFeatures);
 
   // viz::ContextLostObserver Implementation
   void OnContextLost() override;
@@ -139,6 +267,12 @@
                             std::unique_ptr<gfx::GpuFence> gpu_fence);
 
   bool IsFeatureEnabled(device::mojom::XRSessionFeature feature) const;
+#if BUILDFLAG(IS_WIN)
+  D3D11TextureHelper texture_helper_{this};
+#endif
+  int16_t next_frame_id_ = 0;
+  scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
+  std::unordered_set<device::mojom::XRSessionFeature> enabled_features_;
 
   // Owned by OpenXrStatics
   XrInstance instance_;
@@ -147,6 +281,35 @@
   scoped_refptr<viz::ContextProvider> context_provider_;
   VizContextProviderFactoryAsync context_provider_factory_async_;
 
+  FPSMeter fps_meter_;
+  SlidingTimeDeltaAverage webxr_js_time_;
+  SlidingTimeDeltaAverage webxr_gpu_time_;
+
+  absl::optional<OutstandingFrame> pending_frame_;
+
+  bool is_presenting_ = false;  // True if we have a presenting session.
+  bool webxr_visible_ = true;   // The browser may hide a presenting session.
+  bool overlay_visible_ = false;
+  base::OnceCallback<void()> delayed_get_frame_data_callback_;
+
+  gfx::RectF left_webxr_bounds_;
+  gfx::RectF right_webxr_bounds_;
+  gfx::Size source_size_;
+
+  mojo::Remote<mojom::XRPresentationClient> submit_client_;
+  SubmitOverlayTextureCallback overlay_submit_callback_;
+  RequestNotificationOnWebXrSubmittedCallback on_webxr_submitted_;
+  bool webxr_has_pose_ = false;
+  base::RepeatingCallback<void(mojom::XRVisibilityState)>
+      on_visibility_state_changed_;
+  mojo::Receiver<mojom::XRPresentationProvider> presentation_receiver_{this};
+  mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
+  mojo::Receiver<mojom::ImmersiveOverlay> overlay_receiver_{this};
+  mojom::XRVisibilityState visibility_state_ =
+      mojom::XRVisibilityState::VISIBLE;
+  mojom::VRStageParametersPtr current_stage_parameters_;
+  uint32_t stage_parameters_id_;
+
   // Lifetime of the platform helper is guaranteed by the OpenXrDevice.
   raw_ptr<OpenXrPlatformHelper> platform_helper_;
   std::unique_ptr<OpenXrGraphicsBinding> graphics_binding_;
diff --git a/device/vr/windows/compositor_base.cc b/device/vr/windows/compositor_base.cc
deleted file mode 100644
index 837cae9..0000000
--- a/device/vr/windows/compositor_base.cc
+++ /dev/null
@@ -1,672 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/windows/compositor_base.h"
-
-#include "base/functional/bind.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/trace_event/trace_event.h"
-#include "base/types/cxx23_to_underlying.h"
-#include "build/build_config.h"
-#include "components/viz/common/gpu/context_provider.h"
-#include "ui/gfx/geometry/angle_conversions.h"
-#include "ui/gfx/geometry/transform.h"
-
-#if BUILDFLAG(IS_WIN)
-#include "device/vr/windows/d3d11_texture_helper.h"
-#endif
-
-namespace {
-// Number of frames to use for sliding averages for pose timings,
-// as used for estimating prediction times.
-constexpr unsigned kSlidingAverageSize = 5;
-
-device::mojom::XRRenderInfoPtr GetRenderInfo(
-    const device::mojom::XRFrameData& frame_data) {
-  device::mojom::XRRenderInfoPtr result = device::mojom::XRRenderInfo::New();
-
-  result->frame_id = frame_data.frame_id;
-  result->mojo_from_viewer = frame_data.mojo_from_viewer.Clone();
-
-  for (size_t i = 0; i < frame_data.views.size(); i++) {
-    result->views.push_back(frame_data.views[i]->Clone());
-  }
-
-  return result;
-}
-
-}  // namespace
-
-namespace device {
-
-mojom::XRFrameDataPtr XRDeviceAbstraction::GetNextFrameData() {
-  return nullptr;
-}
-void XRDeviceAbstraction::OnSessionStart() {}
-void XRDeviceAbstraction::HandleDeviceLost() {}
-bool XRDeviceAbstraction::HasSessionEnded() {
-  return false;
-}
-void XRDeviceAbstraction::OnLayerBoundsChanged(const gfx::Size& frame_size) {}
-device::mojom::XREnvironmentBlendMode
-XRDeviceAbstraction::GetEnvironmentBlendMode(
-    device::mojom::XRSessionMode session_mode) {
-  return device::mojom::XREnvironmentBlendMode::kOpaque;
-}
-device::mojom::XRInteractionMode XRDeviceAbstraction::GetInteractionMode(
-    device::mojom::XRSessionMode session_mode) {
-  return device::mojom::XRInteractionMode::kWorldSpace;
-}
-bool XRDeviceAbstraction::CanEnableAntiAliasing() const {
-  return true;
-}
-
-XRCompositorCommon::OutstandingFrame::OutstandingFrame() = default;
-XRCompositorCommon::OutstandingFrame::~OutstandingFrame() = default;
-
-XRCompositorCommon::XRCompositorCommon()
-    : XRThread("WindowsXRCompositor"),
-      main_thread_task_runner_(
-          base::SingleThreadTaskRunner::GetCurrentDefault()),
-      webxr_js_time_(kSlidingAverageSize),
-      webxr_gpu_time_(kSlidingAverageSize) {
-  DCHECK(main_thread_task_runner_);
-}
-
-XRCompositorCommon::~XRCompositorCommon() {
-  // Since we derive from base::Thread, all derived classes must call Stop() in
-  // their destructor so the thread gets torn down before any members that may
-  // be in use on the thread get torn down.
-}
-
-void XRCompositorCommon::ClearPendingFrame() {
-  DVLOG(3) << __func__;
-  // Notify the derived class first so it can clear its pending frame before
-  // potentially starting a new frame with delayed_get_frame_data_callback_.
-  ClearPendingFrameInternal();
-
-  pending_frame_.reset();
-  // Send frame data to outstanding requests.
-  if (delayed_get_frame_data_callback_ &&
-      (webxr_visible_ || on_webxr_submitted_)) {
-    // If WebXR is not visible, but the browser wants to know when it submits a
-    // frame, we allow the renderer to receive poses.
-    std::move(delayed_get_frame_data_callback_).Run();
-  }
-}
-
-bool XRCompositorCommon::IsUsingSharedImages() const {
-  return false;
-}
-
-void XRCompositorCommon::SubmitFrameMissing(int16_t frame_index,
-                                            const gpu::SyncToken& sync_token) {
-  DVLOG(3) << __func__ << " frame_index=" << frame_index;
-  TRACE_EVENT_INSTANT0("xr", "SubmitFrameMissing", TRACE_EVENT_SCOPE_THREAD);
-  if (pending_frame_) {
-    // WebXR for this frame is hidden.
-    pending_frame_->waiting_for_webxr_ = false;
-  }
-  webxr_has_pose_ = false;
-#if (BUILDFLAG(IS_ANDROID))
-  // We haven't finished the rendering path on Android yet so are just calling
-  // this to ensure that the page stays un-stuck while we aren't actually
-  // rendering anything.
-  // TODO(alcooper): Clean this up.
-  pending_frame_->webxr_submitted_ = true;
-#endif
-  MaybeCompositeAndSubmit();
-}
-
-void XRCompositorCommon::SubmitFrame(int16_t frame_index,
-                                     const gpu::MailboxHolder& mailbox,
-                                     base::TimeDelta time_waited) {
-  DVLOG(3) << __func__ << " frame_index=" << frame_index;
-  NOTREACHED();
-}
-
-void XRCompositorCommon::SubmitFrameDrawnIntoTexture(
-    int16_t frame_index,
-    const gpu::SyncToken& sync_token,
-    base::TimeDelta time_waited) {
-  DVLOG(3) << __func__ << " frame_index=" << frame_index;
-  NOTREACHED();
-}
-
-bool XRCompositorCommon::MarkFrameSubmitted(int16_t frame_index) {
-  DVLOG(3) << __func__;
-  webxr_has_pose_ = false;
-  // Tell the browser that WebXR has submitted a frame.
-  if (on_webxr_submitted_) {
-    std::move(on_webxr_submitted_).Run();
-  }
-
-  if (!pending_frame_ ||
-      pending_frame_->render_info_->frame_id != frame_index) {
-    // We weren't expecting a submitted frame.  This can happen if WebXR was
-    // hidden by an overlay for some time.
-    if (submit_client_) {
-      submit_client_->OnSubmitFrameTransferred(false);
-      submit_client_->OnSubmitFrameRendered();
-      TRACE_EVENT1("xr", "SubmitFrameTransferred", "success", false);
-    }
-    return false;
-  }
-
-  pending_frame_->waiting_for_webxr_ = false;
-  pending_frame_->webxr_submitted_ = true;
-  pending_frame_->submit_frame_time_ = base::TimeTicks::Now();
-
-  return true;
-}
-
-#if BUILDFLAG(IS_WIN)
-void XRCompositorCommon::SubmitFrameWithTextureHandle(
-    int16_t frame_index,
-    mojo::PlatformHandle texture_handle,
-    const gpu::SyncToken& sync_token) {
-  DVLOG(3) << __func__ << " frame_index=" << frame_index;
-  TRACE_EVENT1("xr", "SubmitFrameWithTextureHandle", "frameIndex", frame_index);
-  if (!MarkFrameSubmitted(frame_index)) {
-    return;
-  }
-
-  base::win::ScopedHandle scoped_handle = texture_handle.is_valid()
-                                              ? texture_handle.TakeHandle()
-                                              : base::win::ScopedHandle();
-  texture_helper_.SetSourceTexture(std::move(scoped_handle), sync_token,
-                                   left_webxr_bounds_, right_webxr_bounds_);
-
-  // Regardless of success - try to composite what we have.
-  MaybeCompositeAndSubmit();
-}
-#endif
-
-void XRCompositorCommon::CleanUp() {
-  DVLOG(1) << __func__;
-  submit_client_.reset();
-  webxr_has_pose_ = false;
-  presentation_receiver_.reset();
-  frame_data_receiver_.reset();
-  overlay_receiver_.reset();
-  StopRuntime();
-}
-
-void XRCompositorCommon::RequestOverlay(
-    mojo::PendingReceiver<mojom::ImmersiveOverlay> receiver) {
-  overlay_receiver_.reset();
-  overlay_receiver_.Bind(std::move(receiver));
-
-  // WebXR is visible and overlay hidden by default until the overlay overrides
-  // this.
-  SetOverlayAndWebXRVisibility(false, true);
-}
-
-void XRCompositorCommon::UpdateLayerBounds(int16_t frame_id,
-                                           const gfx::RectF& left_bounds,
-                                           const gfx::RectF& right_bounds,
-                                           const gfx::Size& source_size) {
-  // Bounds are updated instantly, rather than waiting for frame_id.  This works
-  // since blink always passes the current frame_id when updating the bounds.
-  // Ignoring the frame_id keeps the logic simpler, so this can more easily
-  // merge with vr_shell_gl eventually.
-  left_webxr_bounds_ = left_bounds;
-  right_webxr_bounds_ = right_bounds;
-
-  // Swap top/bottom to account for differences between D3D and GL coordinates.
-  left_webxr_bounds_.set_y(
-      1 - (left_webxr_bounds_.y() + left_webxr_bounds_.height()));
-  right_webxr_bounds_.set_y(
-      1 - (right_webxr_bounds_.y() + right_webxr_bounds_.height()));
-
-  source_size_ = source_size;
-
-  OnLayerBoundsChanged(source_size_);
-}
-
-void XRCompositorCommon::RequestSession(
-    base::RepeatingCallback<void(mojom::XRVisibilityState)>
-        on_visibility_state_changed,
-    mojom::XRRuntimeSessionOptionsPtr options,
-    RequestSessionCallback callback) {
-  webxr_has_pose_ = false;
-  presentation_receiver_.reset();
-  frame_data_receiver_.reset();
-
-  EnableSupportedFeatures(options->required_features,
-                          options->optional_features);
-
-  // Call the subclass's StartRuntime method. Upon completion, StartRuntime will
-  // call the callback passed to its first parameter, start_runtime_callback.
-  // XRCompositorCommon::StartRuntimeFinish. We setup BindOnce such that all of
-  // the parameters give to us here in XRCompositorCommon::RequestSession are
-  // passed through to StartRuntimeFinish so that it can finish the job.
-  StartRuntime(base::BindOnce(&XRCompositorCommon::StartRuntimeFinish,
-                              base::Unretained(this),
-                              std::move(on_visibility_state_changed),
-                              std::move(options), std::move(callback)));
-}
-
-void XRCompositorCommon::StartRuntimeFinish(
-    base::RepeatingCallback<void(mojom::XRVisibilityState)>
-        on_visibility_state_changed,
-    mojom::XRRuntimeSessionOptionsPtr options,
-    RequestSessionCallback callback,
-    bool success) {
-  if (!success) {
-    TRACE_EVENT_INSTANT0("xr", "Failed to start runtime",
-                         TRACE_EVENT_SCOPE_THREAD);
-    main_thread_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), false, nullptr));
-    return;
-  }
-
-  on_visibility_state_changed_ = std::move(on_visibility_state_changed);
-
-  // Queue up a notification to the requester of the current visibility state,
-  // so that it can be initialized to the right value.
-  if (on_visibility_state_changed_) {
-    main_thread_task_runner_->PostTask(
-        FROM_HERE,
-        base::BindOnce(on_visibility_state_changed_, visibility_state_));
-  }
-
-  device::mojom::XRPresentationTransportOptionsPtr transport_options =
-      device::mojom::XRPresentationTransportOptions::New();
-
-  if (IsUsingSharedImages()) {
-    transport_options->transport_method =
-        device::mojom::XRPresentationTransportMethod::DRAW_INTO_TEXTURE_MAILBOX;
-  } else {
-#if BUILDFLAG(IS_WIN)
-    transport_options->transport_method =
-        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEXTURE_HANDLE;
-#else
-    transport_options->transport_method =
-        device::mojom::XRPresentationTransportMethod::SUBMIT_AS_MAILBOX_HOLDER;
-#endif
-  }
-
-  // Only set boolean options that we need. Default is false, and we should be
-  // able to safely ignore ones that our implementation doesn't care about.
-  transport_options->wait_for_transfer_notification = true;
-
-  OnSessionStart();
-
-  auto submit_frame_sink = device::mojom::XRPresentationConnection::New();
-  submit_frame_sink->provider =
-      presentation_receiver_.BindNewPipeAndPassRemote();
-  submit_frame_sink->client_receiver =
-      submit_client_.BindNewPipeAndPassReceiver();
-  submit_frame_sink->transport_options = std::move(transport_options);
-
-  auto session = device::mojom::XRSession::New();
-  session->data_provider = frame_data_receiver_.BindNewPipeAndPassRemote();
-  session->submit_frame_sink = std::move(submit_frame_sink);
-
-  session->enabled_features.insert(session->enabled_features.end(),
-                                   enabled_features_.begin(),
-                                   enabled_features_.end());
-
-  session->device_config = device::mojom::XRSessionDeviceConfig::New();
-  session->device_config->enable_anti_aliasing = CanEnableAntiAliasing();
-  session->device_config->views = GetDefaultViews();
-  session->enviroment_blend_mode = GetEnvironmentBlendMode(options->mode);
-  session->interaction_mode = GetInteractionMode(options->mode);
-
-  main_thread_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), true, std::move(session)));
-  is_presenting_ = true;
-
-#if BUILDFLAG(IS_WIN)
-  texture_helper_.SetSourceAndOverlayVisible(webxr_visible_, overlay_visible_);
-#endif
-}
-
-void XRCompositorCommon::ExitPresent(ExitXrPresentReason reason) {
-  DVLOG(1) << __func__ << " reason=" << base::to_underlying(reason);
-  TRACE_EVENT_INSTANT1("xr", "ExitPresent", TRACE_EVENT_SCOPE_THREAD, "reason",
-                       base::to_underlying(reason));
-  if (!is_presenting_)
-    return;
-
-  is_presenting_ = false;
-  webxr_has_pose_ = false;
-  presentation_receiver_.reset();
-  frame_data_receiver_.reset();
-  submit_client_.reset();
-
-  pending_frame_.reset();
-  delayed_get_frame_data_callback_.Reset();
-
-  // Reset webxr_visible_ for subsequent presentations.
-  webxr_visible_ = true;
-
-  // Kill outstanding overlays:
-  overlay_visible_ = false;
-  overlay_receiver_.reset();
-
-#if BUILDFLAG(IS_WIN)
-  texture_helper_.SetSourceAndOverlayVisible(false, false);
-#endif
-
-  // Don't call StopRuntime until this thread has finished the rest of the work.
-  // This is to prevent the OpenXrApiWrapper from being deleted before its
-  // cleanup work has finished.
-  task_runner()->PostTask(FROM_HERE,
-                          base::BindOnce(&XRCompositorCommon::StopRuntime,
-                                         weak_ptr_factory_.GetWeakPtr()));
-}
-
-void XRCompositorCommon::SetVisibilityState(
-    mojom::XRVisibilityState visibility_state) {
-  if (visibility_state_ != visibility_state) {
-    visibility_state_ = visibility_state;
-    if (on_visibility_state_changed_) {
-      main_thread_task_runner_->PostTask(
-          FROM_HERE,
-          base::BindOnce(on_visibility_state_changed_, visibility_state));
-    }
-  }
-}
-
-const mojom::VRStageParametersPtr&
-XRCompositorCommon::GetCurrentStageParameters() const {
-  return current_stage_parameters_;
-}
-
-void XRCompositorCommon::SetStageParameters(
-    mojom::VRStageParametersPtr stage_parameters) {
-  // If the stage parameters are identical no need to update them.
-  if ((!current_stage_parameters_ && !stage_parameters) ||
-      (current_stage_parameters_ && stage_parameters &&
-       current_stage_parameters_.Equals(stage_parameters))) {
-    return;
-  }
-
-  // If they have changed, increment the ID and save the new parameters.
-  stage_parameters_id_++;
-  current_stage_parameters_ = std::move(stage_parameters);
-}
-
-void XRCompositorCommon::Init() {}
-
-void XRCompositorCommon::StartPendingFrame() {
-  DVLOG(3) << __func__ << " pending_frame_=" << pending_frame_.has_value();
-  if (!pending_frame_) {
-    pending_frame_.emplace();
-    pending_frame_->waiting_for_webxr_ = webxr_visible_;
-    pending_frame_->waiting_for_overlay_ = overlay_visible_;
-    pending_frame_->frame_data_ = GetNextFrameData();
-    // GetNextFrameData() should never return null:
-    DCHECK(pending_frame_->frame_data_);
-    pending_frame_->render_info_ = GetRenderInfo(*pending_frame_->frame_data_);
-  }
-}
-
-void XRCompositorCommon::GetFrameData(
-    mojom::XRFrameDataRequestOptionsPtr options,
-    mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
-  TRACE_EVENT0("xr", "GetFrameData");
-  if (HasSessionEnded()) {
-    ExitPresent(ExitXrPresentReason::kGetFrameAfterSessionEnded);
-    return;
-  }
-
-  // HasSessionEnded() may do some work that alters the state of
-  // `is_presenting_`, in which case we'll get the ExitPresent log to know that
-  // we've ignored this request; but otherwise, we should log the rest of the
-  // state after that.
-  DVLOG(3) << __func__ << " is_presenting_=" << is_presenting_
-           << " webxr_visible=" << webxr_visible_
-           << " on_webxr_submitted_=" << !!on_webxr_submitted_
-           << " webxr_has_pose_=" << webxr_has_pose_;
-
-  if (!is_presenting_) {
-    return;
-  }
-
-  // If we've already given out a pose for the current frame, or aren't visible,
-  // delay giving out a pose until the next frame we are visible.
-  // However, if we aren't visible and the browser is waiting to learn that
-  // WebXR has submitted a frame, we can give out a pose as though we are
-  // visible.
-  if ((!webxr_visible_ && !on_webxr_submitted_) || webxr_has_pose_) {
-    // There should only be one outstanding GetFrameData call at a time.  We
-    // shouldn't get new ones until this resolves or presentation ends/restarts.
-    if (delayed_get_frame_data_callback_) {
-      frame_data_receiver_.ReportBadMessage(
-          "Multiple outstanding GetFrameData calls");
-      return;
-    }
-    delayed_get_frame_data_callback_ = base::BindOnce(
-        &XRCompositorCommon::GetFrameData, base::Unretained(this),
-        std::move(options), std::move(callback));
-    return;
-  }
-
-  StartPendingFrame();
-  webxr_has_pose_ = true;
-  pending_frame_->webxr_has_pose_ = true;
-  pending_frame_->sent_frame_data_time_ = base::TimeTicks::Now();
-
-  // TODO(https://crbug.com/1218135): The lack of frame_data_ here indicates
-  // that we probably should have deferred this call, but it matches the
-  // behavior from before the stage parameters were updated in this function and
-  // avoids a crash. Likely the deferral above should check if we're awaiting
-  // either the webxr or overlay submit.
-  if (pending_frame_->frame_data_) {
-    // If the stage parameters have been updated since the last frame that was
-    // sent, send the updated values.
-    pending_frame_->frame_data_->stage_parameters_id = stage_parameters_id_;
-    if (options->stage_parameters_id != stage_parameters_id_) {
-      pending_frame_->frame_data_->stage_parameters =
-          current_stage_parameters_.Clone();
-    }
-  } else {
-    TRACE_EVENT0("xr", "GetFrameData Missing FrameData");
-  }
-
-  // Yield here to let the event queue process pending mojo messages,
-  // specifically the next gamepad callback request that's likely to
-  // have been sent during WaitGetPoses.
-  task_runner()->PostTask(
-      FROM_HERE, base::BindOnce(&XRCompositorCommon::SendFrameData,
-                                base::Unretained(this), std::move(callback),
-                                std::move(pending_frame_->frame_data_)));
-
-  next_frame_id_ += 1;
-  if (next_frame_id_ < 0) {
-    next_frame_id_ = 0;
-  }
-}
-
-void XRCompositorCommon::SendFrameData(
-    XRFrameDataProvider::GetFrameDataCallback callback,
-    mojom::XRFrameDataPtr frame_data) {
-  DVLOG(3) << __func__;
-  TRACE_EVENT0("xr", "SendFrameData");
-
-  // This method represents a call from the renderer process. If our visibility
-  // state is hidden, we should avoid handing "sensitive" information, like the
-  // pose back up to the renderer. Note that this check is done here as other
-  // methods (RequestNextOverlayPose) represent a call from the browser process,
-  // which should receive the pose.
-  bool is_visible =
-      (visibility_state_ != device::mojom::XRVisibilityState::HIDDEN);
-
-  // We have posted a message to allow other calls to get through, and now state
-  // may have changed.  WebXR may not be presenting any more, or may be hidden.
-  std::move(callback).Run(is_presenting_ && is_visible &&
-                                  (webxr_visible_ || on_webxr_submitted_)
-                              ? std::move(frame_data)
-                              : mojom::XRFrameData::New());
-}
-
-void XRCompositorCommon::GetEnvironmentIntegrationProvider(
-    mojo::PendingAssociatedReceiver<
-        device::mojom::XREnvironmentIntegrationProvider> environment_provider) {
-  // Environment integration is not supported. This call should not
-  // be made on this device.
-  mojo::ReportBadMessage("Environment integration is not supported.");
-}
-
-void XRCompositorCommon::SubmitOverlayTexture(
-    int16_t frame_id,
-    mojo::PlatformHandle texture_handle,
-    const gpu::SyncToken& sync_token,
-    const gfx::RectF& left_bounds,
-    const gfx::RectF& right_bounds,
-    SubmitOverlayTextureCallback overlay_submit_callback) {
-  TRACE_EVENT_INSTANT0("xr", "SubmitOverlay", TRACE_EVENT_SCOPE_THREAD);
-  DCHECK(overlay_visible_);
-  overlay_submit_callback_ = std::move(overlay_submit_callback);
-  if (!pending_frame_) {
-    // We may stop presenting while there is a pending SubmitOverlayTexture
-    // outstanding.  If we get an overlay submit we weren't expecting, just
-    // ignore it.
-    DCHECK(!is_presenting_);
-    std::move(overlay_submit_callback_).Run(false);
-    return;
-  }
-
-  pending_frame_->waiting_for_overlay_ = false;
-
-#if BUILDFLAG(IS_WIN)
-  texture_helper_.SetOverlayTexture(texture_handle.TakeHandle(), sync_token,
-                                    left_bounds, right_bounds);
-  pending_frame_->overlay_submitted_ = true;
-
-  // Regardless of success - try to composite what we have.
-  MaybeCompositeAndSubmit();
-#endif
-}
-
-void XRCompositorCommon::RequestNextOverlayPose(
-    RequestNextOverlayPoseCallback callback) {
-  DVLOG(3) << __func__;
-  // We will only request poses while the overlay is visible.
-  DCHECK(overlay_visible_);
-  TRACE_EVENT_INSTANT0("xr", "RequestOverlayPose", TRACE_EVENT_SCOPE_THREAD);
-
-  // Ensure we have a pending frame.
-  StartPendingFrame();
-  pending_frame_->overlay_has_pose_ = true;
-  std::move(callback).Run(pending_frame_->render_info_->Clone());
-}
-
-void XRCompositorCommon::SetOverlayAndWebXRVisibility(bool overlay_visible,
-                                                      bool webxr_visible) {
-  DVLOG(1) << __func__ << " overlay_visible=" << overlay_visible
-           << " webxr_visible=" << webxr_visible;
-  TRACE_EVENT_INSTANT2("xr", "SetOverlayAndWebXRVisibility",
-                       TRACE_EVENT_SCOPE_THREAD, "overlay", overlay_visible,
-                       "webxr", webxr_visible);
-  // Update state.
-  webxr_visible_ = webxr_visible;
-  overlay_visible_ = overlay_visible;
-  if (pending_frame_) {
-    pending_frame_->waiting_for_webxr_ =
-        pending_frame_->waiting_for_webxr_ && webxr_visible;
-    pending_frame_->waiting_for_overlay_ =
-        pending_frame_->waiting_for_overlay_ && overlay_visible;
-  }
-
-  // Update texture helper.
-#if BUILDFLAG(IS_WIN)
-  texture_helper_.SetSourceAndOverlayVisible(webxr_visible, overlay_visible);
-#endif
-
-  // Maybe composite and submit if we have a pending that is now valid to
-  // submit.
-  MaybeCompositeAndSubmit();
-}
-
-void XRCompositorCommon::RequestNotificationOnWebXrSubmitted(
-    RequestNotificationOnWebXrSubmittedCallback callback) {
-  on_webxr_submitted_ = std::move(callback);
-}
-
-void XRCompositorCommon::MaybeCompositeAndSubmit() {
-  DVLOG(3) << __func__;
-  if (!pending_frame_) {
-    // There is no outstanding frame, nor frame to composite, but there may be
-    // pending GetFrameData calls, so ClearPendingFrame() to respond to them.
-    ClearPendingFrame();
-    return;
-  }
-
-  // Check if we have obtained all layers (overlay and webxr) that we need.
-  if (pending_frame_->waiting_for_webxr_ ||
-      pending_frame_->waiting_for_overlay_) {
-    DVLOG(3) << __func__ << "Waiting for additional layers, waiting_for_webxr_="
-             << pending_frame_->waiting_for_webxr_
-             << " waiting_for_overlay=" << pending_frame_->waiting_for_overlay_;
-    // Haven't received submits from all layers.
-    return;
-  }
-
-  // TODO(https://crbug.com/1454950): Unify OpenXr Rendering paths.
-#if BUILDFLAG(IS_WIN)
-  bool copy_successful = false;
-  bool has_webxr_content = pending_frame_->webxr_submitted_ && webxr_visible_;
-  bool has_overlay_content =
-      pending_frame_->overlay_submitted_ && overlay_visible_;
-  bool can_submit = has_webxr_content || has_overlay_content;
-
-  // Tell texture helper to composite, then grab the output texture, and submit.
-  // If we submitted, set up the next frame, and send outstanding pose requests.
-  if (can_submit) {
-    copy_successful = texture_helper_.UpdateBackbufferSizes() &&
-                      texture_helper_.CompositeToBackBuffer();
-  } else {
-    texture_helper_.CleanupNoSubmit();
-  }
-#elif BUILDFLAG(IS_ANDROID)
-  bool copy_successful = true;
-#endif
-
-  // A copy can only be succesful if we actually tried to submit.
-  if (copy_successful) {
-    pending_frame_->frame_ready_time_ = base::TimeTicks::Now();
-    if (!SubmitCompositedFrame()) {
-      ExitPresent(ExitXrPresentReason::kSubmitFrameFailed);
-      // ExitPresent() clears pending_frame_, so return here to avoid
-      // accessing it below.
-      return;
-    }
-  }
-
-  if (pending_frame_->webxr_submitted_ && copy_successful) {
-    // We've submitted a frame.
-    webxr_js_time_.AddSample(pending_frame_->submit_frame_time_ -
-                             pending_frame_->sent_frame_data_time_);
-    webxr_gpu_time_.AddSample(pending_frame_->frame_ready_time_ -
-                              pending_frame_->submit_frame_time_);
-
-    TRACE_EVENT_INSTANT2(
-        "gpu", "WebXR frame time (ms)", TRACE_EVENT_SCOPE_THREAD, "javascript",
-        webxr_js_time_.GetAverage().InMillisecondsF(), "rendering",
-        webxr_gpu_time_.GetAverage().InMillisecondsF());
-    fps_meter_.AddFrame(base::TimeTicks::Now());
-    TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS());
-  }
-
-  if (pending_frame_->webxr_submitted_ && submit_client_) {
-    // Tell WebVR that we are done with the texture (if we got a texture)
-    submit_client_->OnSubmitFrameTransferred(copy_successful);
-    submit_client_->OnSubmitFrameRendered();
-    TRACE_EVENT1("xr", "SubmitFrameTransferred", "success", copy_successful);
-  }
-
-  if (pending_frame_->overlay_submitted_ && overlay_submit_callback_) {
-    // Tell the browser/overlay that we are done with its texture so it can be
-    // reused.
-    std::move(overlay_submit_callback_).Run(copy_successful);
-  }
-
-  ClearPendingFrame();
-}
-
-}  // namespace device
diff --git a/device/vr/windows/compositor_base.h b/device/vr/windows/compositor_base.h
deleted file mode 100644
index 120bdcc..0000000
--- a/device/vr/windows/compositor_base.h
+++ /dev/null
@@ -1,269 +0,0 @@
-// Copyright 2018 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_WINDOWS_COMPOSITOR_BASE_H_
-#define DEVICE_VR_WINDOWS_COMPOSITOR_BASE_H_
-
-#include "base/memory/scoped_refptr.h"
-#include "base/memory/weak_ptr.h"
-#include "base/task/single_thread_task_runner.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "device/vr/public/mojom/xr_session.mojom.h"
-#include "device/vr/util/fps_meter.h"
-#include "device/vr/util/sliding_average.h"
-#include "device/vr/vr_device.h"
-#include "mojo/public/cpp/bindings/associated_remote.h"
-#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
-#include "mojo/public/cpp/bindings/pending_associated_remote.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/platform/platform_handle.h"
-#include "ui/gfx/geometry/rect_f.h"
-
-#if BUILDFLAG(IS_WIN)
-#include "base/threading/thread.h"
-#include "device/vr/windows/d3d11_texture_helper.h"
-#endif
-
-#if BUILDFLAG(IS_ANDROID)
-#include "base/android/java_handler_thread.h"
-#endif
-
-namespace gpu::gles2 {
-class GLES2Interface;
-}  // namespace gpu::gles2
-
-namespace device {
-
-enum class ExitXrPresentReason : int32_t {
-  kUnknown = 0,
-  kMojoConnectionError = 1,
-  kOpenXrUninitialize = 2,
-  kStartRuntimeFailed = 3,
-  kOpenXrStartFailed = 4,
-  kXrEndFrameFailed = 5,
-  kGetFrameAfterSessionEnded = 6,
-  kSubmitFrameFailed = 7,
-  kBrowserShutdown = 8,
-  kXrPlatformHelperShutdown = 9,
-};
-
-class XRDeviceAbstraction {
- public:
-  virtual mojom::XRFrameDataPtr GetNextFrameData();
-
-  using StartRuntimeCallback = base::OnceCallback<void(bool success)>;
-  virtual void StartRuntime(StartRuntimeCallback start_runtime_callback) = 0;
-  virtual void StopRuntime() = 0;
-  virtual void OnSessionStart();
-  virtual bool HasSessionEnded();
-  virtual bool SubmitCompositedFrame() = 0;
-  virtual void HandleDeviceLost();
-  virtual void OnLayerBoundsChanged(const gfx::Size& size);
-  // Sets enabled_features_ based on what features are supported
-  virtual void EnableSupportedFeatures(
-      const std::vector<device::mojom::XRSessionFeature>& requiredFeatures,
-      const std::vector<device::mojom::XRSessionFeature>& optionalFeatures) = 0;
-  virtual device::mojom::XREnvironmentBlendMode GetEnvironmentBlendMode(
-      device::mojom::XRSessionMode session_mode);
-  virtual device::mojom::XRInteractionMode GetInteractionMode(
-      device::mojom::XRSessionMode session_mode);
-  virtual bool CanEnableAntiAliasing() const;
-  virtual std::vector<mojom::XRViewPtr> GetDefaultViews() const = 0;
-};
-
-#if BUILDFLAG(IS_ANDROID)
-class XRThread : public base::android::JavaHandlerThread {
- public:
-  explicit XRThread(const char* name)
-      : base::android::JavaHandlerThread(name) {}
-  ~XRThread() override = default;
-};
-#elif BUILDFLAG(IS_WIN)
-class XRThread : public base::Thread {
- public:
-  explicit XRThread(const char* name) : base::Thread(name) {}
-  ~XRThread() override = default;
-};
-#else
-#error "Trying to build OpenXR for an unsupported platform"
-#endif
-
-class XRCompositorCommon : public XRThread,
-                           public XRDeviceAbstraction,
-                           public mojom::XRPresentationProvider,
-                           public mojom::XRFrameDataProvider,
-                           public mojom::ImmersiveOverlay {
- public:
-  using RequestSessionCallback =
-      base::OnceCallback<void(bool result, mojom::XRSessionPtr)>;
-
-  XRCompositorCommon();
-
-  XRCompositorCommon(const XRCompositorCommon&) = delete;
-  XRCompositorCommon& operator=(const XRCompositorCommon&) = delete;
-
-  ~XRCompositorCommon() override;
-
-  void RequestSession(base::RepeatingCallback<void(mojom::XRVisibilityState)>
-                          on_visibility_state_changed,
-                      mojom::XRRuntimeSessionOptionsPtr options,
-                      RequestSessionCallback callback);
-  void ExitPresent(ExitXrPresentReason reason);
-
-  void GetFrameData(mojom::XRFrameDataRequestOptionsPtr options,
-                    XRFrameDataProvider::GetFrameDataCallback callback) final;
-
-  void GetEnvironmentIntegrationProvider(
-      mojo::PendingAssociatedReceiver<
-          device::mojom::XREnvironmentIntegrationProvider> environment_provider)
-      override;
-
-  void RequestOverlay(mojo::PendingReceiver<mojom::ImmersiveOverlay> receiver);
-
-  virtual gpu::gles2::GLES2Interface* GetContextGL() = 0;
-
- protected:
-  void SetVisibilityState(mojom::XRVisibilityState visibility_state);
-  const mojom::VRStageParametersPtr& GetCurrentStageParameters() const;
-  void SetStageParameters(mojom::VRStageParametersPtr stage_parameters);
-#if BUILDFLAG(IS_WIN)
-  D3D11TextureHelper texture_helper_{this};
-#endif
-  int16_t next_frame_id_ = 0;
-
-  // Allow derived classes to call methods on the main thread.
-  scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
-
-  // Derived classes override this to be notified to clear its pending frame.
-  virtual void ClearPendingFrameInternal() {}
-
-  std::unordered_set<device::mojom::XRSessionFeature> enabled_features_;
-
-  // Override the default of false if you wish to use shared buffers across
-  // processes
-  virtual bool IsUsingSharedImages() const;
-
-  // XRPresentationProvider overrides:
-  void SubmitFrame(int16_t frame_index,
-                   const gpu::MailboxHolder& mailbox,
-                   base::TimeDelta time_waited) override;
-  void SubmitFrameDrawnIntoTexture(int16_t frame_index,
-                                   const gpu::SyncToken&,
-                                   base::TimeDelta time_waited) override;
-  void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) final;
-#if BUILDFLAG(IS_WIN)
-  void SubmitFrameWithTextureHandle(int16_t frame_index,
-                                    mojo::PlatformHandle texture_handle,
-                                    const gpu::SyncToken& sync_token) final;
-#endif
-
-  // Will Submit if we have textures submitted from the Overlay (if it is
-  // visible), and WebXR (if it is visible).  We decide what to wait for during
-  // StartPendingFrame, may mark things as ready after SubmitFrameMissing and
-  // SubmitFrameWithTextureHandle (for WebXR), or SubmitOverlayTexture (for
-  // overlays), or SetOverlayAndWebXRVisibility (for WebXR and overlays).
-  // Finally, if we exit presentation while waiting for outstanding submits, we
-  // will clean up our pending-frame state.
-  void MaybeCompositeAndSubmit();
-
-  // Sets all relevant internal state to mark that we have successfully received
-  // a frame. Will return whether or not the given frame index was expected.
-  // If not expected, not all state may be successfully cleared.
-  bool MarkFrameSubmitted(int16_t frame_index);
-
- private:
-  // base::Thread overrides:
-  void Init() final;
-  void CleanUp() final;
-
-  void ClearPendingFrame();
-  void StartPendingFrame();
-
-  void StartRuntimeFinish(
-      base::RepeatingCallback<void(mojom::XRVisibilityState)>
-          on_visibility_state_changed,
-      mojom::XRRuntimeSessionOptionsPtr options,
-      RequestSessionCallback callback,
-      bool success);
-
-  // XRPresentationProvider overrides:
-  void UpdateLayerBounds(int16_t frame_id,
-                         const gfx::RectF& left_bounds,
-                         const gfx::RectF& right_bounds,
-                         const gfx::Size& source_size) final;
-
-  // ImmersiveOverlay:
-  void SubmitOverlayTexture(int16_t frame_id,
-                            mojo::PlatformHandle texture,
-                            const gpu::SyncToken& sync_token,
-                            const gfx::RectF& left_bounds,
-                            const gfx::RectF& right_bounds,
-                            SubmitOverlayTextureCallback callback) override;
-  void RequestNextOverlayPose(RequestNextOverlayPoseCallback callback) override;
-  void SetOverlayAndWebXRVisibility(bool overlay_visible,
-                                    bool webxr_visible) override;
-  void RequestNotificationOnWebXrSubmitted(
-      RequestNotificationOnWebXrSubmittedCallback callback) override;
-
-  void SendFrameData(XRFrameDataProvider::GetFrameDataCallback callback,
-                     mojom::XRFrameDataPtr frame_data);
-
-  struct OutstandingFrame {
-    OutstandingFrame();
-    ~OutstandingFrame();
-    bool webxr_has_pose_ = false;
-    bool overlay_has_pose_ = false;
-    bool webxr_submitted_ = false;
-    bool overlay_submitted_ = false;
-    bool waiting_for_webxr_ = false;
-    bool waiting_for_overlay_ = false;
-
-    mojom::XRFrameDataPtr frame_data_;
-    mojom::XRRenderInfoPtr render_info_;
-
-    base::TimeTicks sent_frame_data_time_;
-    base::TimeTicks submit_frame_time_;
-    base::TimeTicks frame_ready_time_;
-  };
-
-  FPSMeter fps_meter_;
-  SlidingTimeDeltaAverage webxr_js_time_;
-  SlidingTimeDeltaAverage webxr_gpu_time_;
-
-  absl::optional<OutstandingFrame> pending_frame_;
-
-  bool is_presenting_ = false;  // True if we have a presenting session.
-  bool webxr_visible_ = true;   // The browser may hide a presenting session.
-  bool overlay_visible_ = false;
-  base::OnceCallback<void()> delayed_get_frame_data_callback_;
-
-  gfx::RectF left_webxr_bounds_;
-  gfx::RectF right_webxr_bounds_;
-  gfx::Size source_size_;
-
-  mojo::Remote<mojom::XRPresentationClient> submit_client_;
-  SubmitOverlayTextureCallback overlay_submit_callback_;
-  RequestNotificationOnWebXrSubmittedCallback on_webxr_submitted_;
-  bool webxr_has_pose_ = false;
-  base::RepeatingCallback<void(mojom::XRVisibilityState)>
-      on_visibility_state_changed_;
-  mojo::Receiver<mojom::XRPresentationProvider> presentation_receiver_{this};
-  mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
-  mojo::Receiver<mojom::ImmersiveOverlay> overlay_receiver_{this};
-  mojom::XRVisibilityState visibility_state_ =
-      mojom::XRVisibilityState::VISIBLE;
-  mojom::VRStageParametersPtr current_stage_parameters_;
-  uint32_t stage_parameters_id_;
-
-  base::WeakPtrFactory<XRCompositorCommon> weak_ptr_factory_{this};
-};
-
-}  // namespace device
-
-#endif  // DEVICE_VR_WINDOWS_COMPOSITOR_BASE_H_
diff --git a/device/vr/windows/d3d11_texture_helper.cc b/device/vr/windows/d3d11_texture_helper.cc
index 2b551241..96da3dba 100644
--- a/device/vr/windows/d3d11_texture_helper.cc
+++ b/device/vr/windows/d3d11_texture_helper.cc
@@ -6,7 +6,7 @@
 
 #include "base/trace_event/common/trace_event_common.h"
 #include "base/trace_event/trace_event.h"
-#include "device/vr/windows/compositor_base.h"
+#include "device/vr/openxr/openxr_render_loop.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/common/constants.h"
 #include "mojo/public/c/system/platform_handle.h"
@@ -72,8 +72,8 @@
 D3D11TextureHelper::LayerData::LayerData() = default;
 D3D11TextureHelper::LayerData::~LayerData() = default;
 
-D3D11TextureHelper::D3D11TextureHelper(XRCompositorCommon* compositor)
-    : compositor_(compositor) {}
+D3D11TextureHelper::D3D11TextureHelper(OpenXrRenderLoop* render_loop)
+    : render_loop_(render_loop) {}
 
 D3D11TextureHelper::~D3D11TextureHelper() {}
 
@@ -205,7 +205,7 @@
       // until GPU process has passed the sync token. This must happen before
       // AcquireSync(0) below otherwise the GPU process will be unable to
       // acquire the mutex and work will happen out of order.
-      gpu::gles2::GLES2Interface* gl = compositor_->GetContextGL();
+      gpu::gles2::GLES2Interface* gl = render_loop_->GetContextGL();
       gl->WaitSyncTokenCHROMIUM(
           render_state_.source_.sync_token_.GetConstData());
       gl->Finish();
@@ -228,7 +228,7 @@
       // until GPU process has passed the sync token. This must happen before
       // AcquireSync(0) below otherwise the GPU process will be unable to
       // acquire the mutex and work will happen out of order.
-      gpu::gles2::GLES2Interface* gl = compositor_->GetContextGL();
+      gpu::gles2::GLES2Interface* gl = render_loop_->GetContextGL();
       gl->WaitSyncTokenCHROMIUM(
           render_state_.overlay_.sync_token_.GetConstData());
       gl->Finish();
diff --git a/device/vr/windows/d3d11_texture_helper.h b/device/vr/windows/d3d11_texture_helper.h
index c2cb5925..1b12c34 100644
--- a/device/vr/windows/d3d11_texture_helper.h
+++ b/device/vr/windows/d3d11_texture_helper.h
@@ -16,11 +16,11 @@
 
 namespace device {
 
-class XRCompositorCommon;
+class OpenXrRenderLoop;
 
 class D3D11TextureHelper {
  public:
-  explicit D3D11TextureHelper(XRCompositorCommon* compositor);
+  explicit D3D11TextureHelper(OpenXrRenderLoop* render_loop);
   ~D3D11TextureHelper();
 
   void Reset();
@@ -106,7 +106,7 @@
     LayerData overlay_;
   };
 
-  const raw_ptr<XRCompositorCommon> compositor_;
+  const raw_ptr<OpenXrRenderLoop> render_loop_;
 
   bool overlay_visible_ = true;
   bool source_visible_ = true;
diff --git a/docs/security/shepherd.md b/docs/security/shepherd.md
index 5d399ba1..b4830cdf 100644
--- a/docs/security/shepherd.md
+++ b/docs/security/shepherd.md
@@ -1,139 +1,178 @@
 # Security Shepherd
 
+## What is Security Shepherding?
+Security Shepherding is a rotational assignment for security bug triage
+(Primary Shepherd) and managing the flow of incoming inquiries and progressing
+security issues (Secondary Shepherd). The Shepherding rota pool is made up of
+people actively working on security in Chrome.
+
+All Security Shepherds are Googlers; therefore, some links on this page may
+not be externally accessible or even further restricted to just Chrome Security
+Googlers.
+
+There is a Primary and Secondary Shepherd scheduled each rotation, with two
+rotations each week, one Tuesday - Thursday and Friday - Monday.
+
+Security Shepherding is *not* an on-call rotation. There’s no pager duty, nor
+are you expected to perform Shepherding duties outside of your usual working
+hours, such as overnight or on holidays, weekends, or other off time.
+
+Shepherds are only responsible for triage of security bugs during your shift.
+You are not responsible for bug triage or updating partially triaged bugs past
+your shift, unless you have specifically taken ownership of an issue, such as
+due to a complicated or OS-specific reproduction, and arranged that with the
+oncoming shepherd. All shepherds should use the handoff doc to communicate items
+for handover; however, the oncoming primary shepherd should operate on the
+premise all new or _under_-triaged issues are your responsibility. Please do not
+leave any unaddressed red cells in the dashboard at the end of your shift.
+
+# TL;DR Checklist for Primary Shepherding
+(“I’m Primary Shepherd, what do I do???”)
+
+The Primary Security Shepherd is the front line of security bug triage during
+their shift. The goal is to triage incoming security issues accurately,
+thoroughly, and quickly (_as quickly as realistically possible_).
+
+Your PRIMARY DIRECTIVE as PRIMARY SHEPHERD is to tackle all the red cells on the
+security bug dashboard.
+
+For [*every new incoming security bug*](#Every-New-Incoming-Security-Bug):
+* Make sure it's [*self-contained*](#Ensure-self-contained-issue).
+* Make sure the report is [*valid and actionable*](#Confirm-valid-and-actionable)
+  * Ideally, you’ll be able to do this by [reproducing the bug](#Reproduce-the-bug),
+    more ideally, [with ClusterFuzz](clusterfuzz-for-shepherds.md).
+* Set [*severity*](#Set-Severity).
+* Set [*oldest impacted active release channel*](#Set-oldest-impacted-active-release-channel) – AKA FoundIn.
+* Set [*impacted-operating-systems*](#Set-impacted-operating-systems).
+* [*Assign*](#Assign) to an appropriate or suitable owner or engineering team.
+
+All of the above should be completed as soon as possible during your shift,
+and at least, by the [shift-handoff](#shift-handoff).
+
+One or more of the above actions may necessary to complete the triage of an
+under-triaged bug, i.e. covering any of the open red cells in the dashboard that
+were not completed from ClusterFuzz auto-triage or previous work on the bug.
+
+All this is hard, so please remember to [ask for help](#Ask-for-help).
+[Yell if you must](https://www.youtube.com/watch?v=5y_SbnPx_cE&t=37s)!
+
+# TL;DR Checklist for Secondary Shepherding
+(“I’m Secondary Shepherd, what do I do???”)
+
+* [*Check in on triaged issues*](#Check-In-On-Triaged-Issues] to ensure progress
+  is being made on medium+ severity security bugs.
+* [*Manage incoming security email*](#Manage-incoming-security-email).
+
+
 [TOC]
 
-## Important Links
+## Links to Helpful Resources
 
-[Chrome Open Security Bugs dashboard,
-go/chrome-security-bugs](http://go/chrome-security-bugs).
+Here are some of the importance references and resources you need or may need
+during your shepherding shift:
 
-[Vulnerability Severity Guidelines](severity-guidelines.md).
+* [Current Shepherds](https://script.google.com/a/macros/google.com/s/AKfycbz02xD4ghSzZu_tXyNRgjC95wFURATZeD_FHq0KRMHeqA-b0b9sow4NV1lhi0P2vy1j/exec)
+* [Chrome Security Bug Dashboard](https://goto.google.com/chrome-security-bugs}
+* [Security Severity Guidelines](severity-guidelines.md)
+* [Security Labels](security-labels.md)
+* FAQs addressing commonly-raised questions about security and what is / is not
+  considered a security bug, to see if there is an existing stance:
+  * [Chrome Security FAQ](faq.md)
+  * [Extensions Security FAQ](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/extensions/docs/security_faq.md)
+  * [Service Worker Security FAQ](service-worker-security-faq.md)
+* [Redshell for Security Shepherds](https://goto.google.com/redshell-for-chrome-shepherds)
+* [Shepherding Guidelines Changelog](https://goto.google.com/shepherding-changelog) for highlighting
+  any process or policy changes since your last shift.
+* [Guidance for triage of theoretical or speculative issues](https://goto.google.com/chrome-speculative-bug-triage)
+* [Reference for common questions about security bug lifecycle](life-of-a-security-issue.md)
+[Reference for questions related to security fix merge process](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/process/merge_request.md#Security-merge-triage)
+  for answering questions (you do not need to approve merges).
+* [Shepherding Handoff Log](https://goto.google.com/chrome-security-shepherd-handoff)
 
-[Security Labels](security-labels.md).
+### Every New Incoming Security Bug
 
-[Current Shepherds](http://go/whos-the-shepherd).
+Monitor the [Chrome open security bugs dashboard](http://go/chrome-security-bugs).
+Tackle all the empty red cells. New bugs populate at the top of the sheet and
+will need full triage. Partially triaged bugs, such as those triaged by
+ClusterFuzz or ones pending updates from a prior shift, may be lower in the
+sheet. Please check the sheet for any red cells and do your best to get any bugs
+to a fully triaged state.
 
-[Shepherd Handoff Log](http://go/chrome-security-shepherd-handoff).
+We aim to have every bug triaged and assigned **within two business days,
+preferably one.** This does not include weekends, but please ensure you leave a
+clear queue before the weekend or a holiday.
 
-You might also like the [HOWTO: Be A Security Shepherd
-deck](https://docs.google.com/presentation/d/1eISJXxyv7dUCGUKk_rvUI9t9s2xb98QY4d_-dZSa7Wg/edit#slide=id.p).
+### Ensure self-contained issue
 
-The [Chrome Security FAQ](faq.md), [Extensions Security
-FAQ](/extensions/docs/security_faq.md), and [Service Worker Security
-FAQ](service-worker-security-faq.md) include commonly-raised questions about
-security and what is or is not considered a security bug. When triaging new
-bugs, you may want to reference these to see if there's an established stance.
+There should be one complete, self-contained report, per root cause. To ensure
+this is the case when assigning security bugs to engineering teams, you may
+need to take some specific actions here:
+* If the report is a bug chain with several underlying causes, **open one new
+  bug per root cause** and marked the parent bug as `blocked on` each. The parent
+  bug should be set to the severity of the full chain. Each child bug may have a
+  lower severity.
+  * If taking these actions for a VRP reported issue, add the `reward_to`
+    label with that reporter's email address and cc: them on the parent issue so
+    they have access.
+* Get everything you need from a reporter before you try and reproduce - do not
+  feel bad about asking for a clear or minimized POC or repeatable steps before
+  attempting to reproduce.
+* If complicated user gestures are required, encourage the reporter to upload a
+  short video. This will alleviate a lot of back and forth for both them and us.
 
-## What Is A Security Shepherd?
+## Confirm Valid and Actionable
 
-A security shepherd is a member of a rotation that occurs in two different time
-slots: Tues to Thurs or Fri to Mon. There is a primary and secondary shepherd on
-each rotation. All shepherds are Googlers and so some links on this page might
-not be externally accessible (or indeed locked down to just Chrome Security
-Googlers).
+We expect engineering teams to address security bugs promptly. In order to do
+that, our goal is to pass them actionable reports with little ambiguity.
 
-[Here is the rotation
-schedule](https://docs.google.com/spreadsheets/d/10sLYZbi6QfLcXrhO-j5eSc82uc7NKnBz_o1pR9y8h7U/edit#gid=0).
+*For each bug, please take the appropriate action, either:*
 
-Shepherds ensure that all incoming security issues are triaged
-quickly and correctly. We aim to have every bug triaged and assigned **within
-two business days** (preferably one). This does not include weekends, but please
-ensure you leave a clear queue before the weekend (i.e. on Friday, unless there
-is a holiday) and check first thing after the weekend (i.e. on Monday morning,
-unless there is a holiday).
+* **WontFix it as invalid** (Many recurring types of invalid reports are covered
+  by the [Security FAQ](faq.md), such as those related physically local attacks
+  or inputting JavaScript in the URL bar or running Javascript directly in
+  DevTools not being an indication of an XSS vulnerability. Mark as WontFix and
+  remove the `Restrict-View-SecurityTeam` label.
+* **Mark as duplicate** – we want exactly one bug per root cause problem. Please
+  check for duplicate issues of a given issue from that or other reporters /
+  sources (such as ClusterFuzz).
+  * Search for similar stack traces or sharing similar keyword traits in the bug
+    tracker.
+  * If there are two open reports of the same issue, please merge as a duplicate
+    in the direction of the oldest report.
+* **Convert functional bugs to Type=Bug** For example, many reports are for
+  crashes of a functional nature, rather than an exploitable security condition,
+  such as most null pointer dereferences. Convert such reports from
+  Type=Bug-Security to Type=Bug. Remove `Restrict-View-SecurityTeam`, but
+  consider adding other view restrictions, such as ‘Restrict-View-EditIssue’ if
+  the immediate disclosure could result in potential abuse (e.g. denial of
+  service issue).
+* **Convert to a privacy bug** - privacy issues (such as issues with incognito)
+  are not considered security bugs, but functional privacy issues.
+  Convert to Type=Bug and add the Privacy component. Add yourself and any other
+  security team members who may potentially need access to the cc: line.
+  Remove `Restrict-View-SecurityTeam` and add `Restrict-View-ChromePrivacy`.
+* **Add `Needs-Feedback` and set a next action date of 24-48 hours for more
+  information** if there is no response, close the issue as `WontFix`.
+* **Determine issue to be theoretical** - and follow the
+  [process for such issues](http://go/chrome-speculative-bug-triage) – theoretical
+  issues are ones that appear to be potentially real bugs, but there the report
+  is lacking evidence of exploitability or reachability. These cases can be
+  shared with engineering teams with a very clear message conveying the
+  speculative nature of the issue. These reports should generally not be
+  prioritized as a Pri-1 as they do not warrant disruption to the engineering
+  teams to investigate and prioritize without more or new information to
+  demonstrate conditions of exploitability.
 
+None of these apply? Great – this means the bug may be valid and actionable!
+It can take multiple discussions with a reporter to understand a bug. Try really
+hard to reach a conclusion by the end of your shift. If this isn’t possible,
+please discuss outstanding cases with the next shepherd and don’t let bugs fall
+through the cracks. You are responsible for any bug reported or in an un-triaged
+state during your shift.
 
-## When Am I The Primary or Secondary Shepherd?
-
-You should get a calendar invite. Please accept it to acknowledge. If you need
-to swap shifts, ask around for a volunteer and then just update the
-[rotation sheet](https://docs.google.com/spreadsheets/d/10sLYZbi6QfLcXrhO-j5eSc82uc7NKnBz_o1pR9y8h7U/edit#gid=0)
-and wait 10 minutes for the calendar invites to be updated.
-
-## I'm The Security Primary or Secondary Shepherd. What Do I Do?
-
-Each week has a primary and secondary, and during their rotation both have
-various important responsibilities:
-
-### Primary Shepherd
-
-* Look at every incoming security bug report on the
-  [dashboard](http://go/chrome-security-bugs). Ensure each is accurately
-  triaged, and actively progressing towards getting fixed.
-* Don't forget to fully triage the low severity bugs. Once a bug is labeled with
-  `Security_Severity-Low `, it disappears from the first sheet and may slip
-  under your radar.
-* Keep the [Shepherd Handoff Log](http://go/chrome-security-shepherd-handoff) up
-  to date.
-* Shout for help if the incoming bug rate is too high ([suggested vocal
-  exercises](https://youtu.be/5y_SbnPx_cE?t=37s)). The first person to ask is
-  the secondary.
-* Make sure all **new bug reports** are triaged completely. That means no red
-  cells on the top of the dashboard. Double-check that OS flags are set
-  properly. For most of the bugs, typically more than one OS is affected, but
-  the dashboard will not highlight it in red.
-* Stay sharp, keep in shape ([hand-stand
-  pushups](https://www.youtube.com/watch?v=jZ1ZDlLImF8#t=50) are standard for
-  the primary shepherd), and remember you may be [called upon during
-  emergencies](https://www.youtube.com/watch?v=buHaKYL9Jhg).
-
-### Secondary Shepherd
-
-* Ensure that all incoming queries to the
-  [security@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/security),
-  [security-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/security-dev),
-  and
-  [chrome-security@google.com](https://groups.google.com/a/google.com/forum/#!forum/chrome-security)
-  lists get a reply (by someone; not necessarily the secondary themselves). See
-  [go/chrome-security-emails](https://goto.google.com/chrome-security-emails)
-  for a dashboard.
-  * Note: external emails will always come in on security@chromium.org or
-    security-dev@chromium.org, as chrome-security@google.com is a Google-only
-    list, but all need to be triaged.
-  * When triaging an email to be handled off of the list, make sure to bcc: the
-    list that it arrived on, so that other people including future secondaries can
-    see that it has been handled.
-  * Some of these emails are requests for inclusion of third party code.
-    By the time you hand over to the next Secondary, please
-    ensure these are either completed or have been acknowledged by some other
-    owner. If not, you may need to do them yourself. Please see
-    [How to do Chrome Third-Party Security Reviews](https://goto.google.com/how-to-do-chrome-third-party-security-reviews)
-    for hints.
-* Look at the open security bug reports and check that progress is occurring.
-  This does not apply to the **new bug reports** (these are handled by the
-  primary shepherd). The rule of thumb is *if there is any red cell on the dashboard, it
-  needs your attention*: that especially includes the "last updated" column.
-  (Our [severity guidelines](severity-guidelines.md) contain the expected duration
-  for shipping fixes, but remember, to get a fix to all users in - say - 60
-  days may require us to land a fix in a week or two).
-  Hints:
-  * Don't just add a comment to the bug: sometimes they can disappear into spam.
-    (Although a hand-crafted, meaningful comment can be effective).
-  * Contact via chat or e-mail (ideally, also comment on the bug so other secondaries
-    can see you did so).
-  * CC more people!
-  * Think about what you can do to unblock the bug. What would _you_ do next?
-    Perhaps you can bring in different experts, suggest a different way to
-    reproduce the bug, or even write a fuzzer? Sometimes your security perspective
-    can really help engineering see a different way forward.
-  * Consider whether it's better for you to make meaningful steps forward on
-    ten bugs than to add ignorable nag messages to twenty bugs.
-  * You can't possibly hope to meaningfully move all bugs forward. As a rule of
-    thumb, perhaps expect to spend a solid ten hours progressing bugs during
-    your shift.
-  * Use the 'last updated' column to avoid duplicating the work of the previous
-    secondary.
-* Stay sharp, keep in shape ([finger
-  exercises](https://youtu.be/iV9vyacIeEY?t=47s) are standard for the secondary),
-  and remember you may be called upon during emergencies.
-
-## Life Of A Security Bug
-
-Do as much as you can for the week to triage, shepherd, and wrap up open
-security bugs. What follows are the details of what that entails, but it
-practically means turning all the red cells in the dashboard to green. **If
-you're ever stuck or in doubt, ask for help on #chrome-security! or the
-[Chrome Security Chat](http://go/chrome-security-chat).**
+The best way determine the validity of a security bug is to [*reproduce it*](#Reproduce-the-bug).
+It’s helpful to remember that reporters invested time and energy in their bug
+reports:
 
 ![alt text](apf-right-a-wrong.png "felt: a lot of Chrome vuln reports come from
 well-meaning people who clearly went out of their way to try to right a wrong.
@@ -141,76 +180,140 @@
 
 [link](https://twitter.com/__apf__/status/728776130564526080)
 
-### Diagnose The Issue
 
-![alt text](sheriff-life-of-an-issue.png "Life of a security issue.")
+If you have to close it, please give an explanation as to why.
 
-* **If the report is invalid**, remove the **Restrict-View-SecurityTeam** label
-  and mark it **WontFix**.
-* **If the report is a duplicate**, mark it **Duplicate**. If the issue this is
-  a duplicate of is public, remove the **Restrict-View-SecurityTeam** label.
-* **If the report is primarily a privacy issue**, send it to the privacy team:
-  * Add the **Privacy** component so that it enters their triage queue.
-  * Change **Type-Bug-Security** to **Type-Bug**.
-  * CC any security team members, including yourself, who may be interested in
-    the privacy issue.
-	* Change the **Restrict-View-SecurityTeam** label to
-  **Restrict-View-ChromePrivacy**.
-    * Note that security team members don't automatically have privacy bug
-      access, so this will probably make the issue inaccessible to you.
-* **If the report is asking about why something is or is not on the Safe
-  Browsing list:**
-  * Close the bug and request the reporter submit the URL to SafeBrowsing.
-  * See below for reporting URLs to SafeBrowsing.
-* **If the report is a potentially valid bug but is not a security
-  vulnerability:**
-  * remove the **Restrict-View-SecurityTeam** label. If necessary, add one of
-    the other **Restrict-View-?** labels:
-    * **Restrict-View-Google** if this is a crash report.
-    * **Restrict-View-EditIssue** if the bug can be abused (e.g. denial of
-      service)
-	* Change **Type-Bug-Security** label to **Type-Bug** (or whatever **Type-?**
-    is appropriate).
-  * Add appropriate component or CCs to ensure it does get triaged.
-  * Add the **Security** component or the **Team-Security-UX** label if the
-    security team should still track the issue (e.g. security features).
-  * Please do not remove the **external_security_report** label.
-* **If the report doesn't have enough information**, ask the reporter for more
-  information, add the **Needs-Feedback** label and wait for 24 hours for a
-  response.
-* The [security bug template](https://bugs.chromium.org/p/chromium/issues/entry?template=Security+Bug)
-  asks reporters to **attach files directly**, not in zip or other archives, and
-  not hosted at an external resource (e.g. Google Cloud Storage). If the report
-  mentions an online demo hosted somewhere, make sure the reporters attach the
-  source code for the demo as well.
-* **If the bug is a security bug, but is only applicable to Chrome OS**:
-	* The Chrome OS Security team now has their own sheriffing rotation. To get
-    bugs into their triage queue, just set OS to the single value of "Chrome".
-    No other steps or labels are needed.
-	* If you need to ping or ask about Chrome OS bug, [ask their current
-    sheriff](http://go/whos-the-chromeos-sheriff).
-* **If the report smells like a vulnerability, keep going.**
+### Reproduce the bug
 
-### Verify And Label The Bug
+Reproducing the bug isn’t always required, but often it’s needed and the only
+way to:
 
-#### Step 1. Reproduce legitimate-sounding issues.
+* Understand a report and validate the issue being presented.
+* Provide actionable information to the engineering team responsible for fixing
+  the bug.
+* Setting the oldest impacted release channel correctly.
 
-Ideally, primary shepherds should reproduce each bug before triaging, but being efficient
-is also important. It's fine to delegate reproducing bugs in the following
-cases:
+These things must be done correctly, so as Security Shepherd, you’ll spend a lot
+of time reproducing bugs. Here are some tips in doing so:
 
-* A bug comes from an automated infrastructure (such as ClusterFuzz or Vomit).
-* A bug comes from a reporter with a solid track record of vulnerabilities (e.g.
-  prolific external researchers or Google Project Zero team).
-* A bug requires a particular device that you don't have available, or any other
-  environment which you don't have ready but a potential code owner would have.
+* Assume that test cases may be malicious. You should only reproduce bugs
+  on your local machine if you're completely certain that you understand
+  100% of the test case. If not, use a disposable virtual machine. If you're
+  inside Google, a good way to do this is using
+  [Redshell](https://goto.google.com/redshell-for-chrome-shepherds).
+* For any sort of a crash, CHECK/DCHECK or memory safety problem
+  [use ClusterFuzz](clusterfuzz-for-shepherds.md). As well as reproducing bugs,
+  ClusterFuzz will help you with lots of subsequent bisection and labelling
+  tasks. Currently ClusterFuzz cannot guard against malicious test cases,
+  so be just as paranoid as if you were running a test case locally.
+* [Instructions for using an Android emulator can be found
+  here](/docs/android_emulator.md). If you're inside Google, we have a
+  [guide for testing using Google infrastructure](https://goto.google.com/android-for-chrome-shepherds).
+* When you can't just build from a specific branch locally, see
+  [https://dev.chromium.org/getting-involved/dev-channel](https://dev.chromium.org/getting-involved/dev-channel)
+  or
+  [https://commondatastorage.googleapis.com/chromium-browser-asan/index.html](https://commondatastorage.googleapis.com/chromium-browser-asan/index.html)
+  for the latest release of a specific version.
+* The [get_asan_chrome.py](https://source.chromium.org/chromium/chromium/src/+/main:tools/get_asan_chrome/get_asan_chrome.py)
+  helper script is a handy way to download ASAN Chrome. The --help flag
+  provides usage instructions, e.g. to fetch builds for various versions and
+  platforms.
+* If you run into issues with a reproducible ClusterFuzz test case (like
+  missing symbols, or if anything else seems off), try uploading the test case
+  again using a different job type with a more mature tool (e.g. ASan on Linux).
+  It may give more complete information.
 
-Mention explicitly in your comment that you didn't reproduce a bug before
-assigning it to someone else.
+### Set severity
 
-A few components have their own triage processes or points of contact who can
-help.
+Use the [Security Severity Guidelines](severity-guidelines.md).
 
+If you can, [*reproduce it using ClusterFuzz*](clusterfuzz-for-shepherds.md), as
+the severity is usually set automatically.
+
+For V8 issues, you can tentatively set the issue as High severity – see [Assign,
+below](#Assign).
+
+Please adjust severity as your understanding of the bug evolves - but please
+always add a comment explaining the change. Higher severity bugs involve
+significant disruption for multiple teams; lower severity issues may not be
+fixed and a fix released to users as quickly as may be warranted. That’s why
+it’s important to get the severity as correct as possible.
+
+### Set oldest impacted active release channel
+
+We do not release severe security regressions, so we need to know the earliest
+impacted Chrome release branch.
+
+First, if an issue [doesn’t impact Chrome users by default, add the label
+**`Security_Impact-None`**](security-labels.md#when-to-use-security_impact_none-toc_security_impact_none);
+otherwise, set a **FoundIn** label as follows:
+
+Check [ChromiumDash](https://chromiumdash.appspot.com/releases?platform=Windows) for the earliest relevant milestone number
+(Extended Stable or Stable – sometimes they are the same).
+* If that branch is affected, set `FoundIn-MMM` in where `MMM` is that milestone
+  number.
+* Otherwise, move forward through milestone numbers. Set `FoundIn-MMM` to the
+  oldest impacted branch you find.
+
+There is no general reason to test versions older than the current Extended
+Stable milestone. If you can [*reproduce using ClusterFuzz*](clusterfuzz-for-shepherds.md)
+the `FoundIn` can often be set automatically if ClusterFuzz can identify the
+culprit CL.
+
+Otherwise, you may need to [reproduce the bug](#Reproduce-the-bug) manually to
+determine the impacted branches.
+
+If you have a bisection or other convincing evidence, that’s sufficient. You can
+manually check which milestone has a given commit in
+[ChromiumDash commits check](https://chromiumdash.appspot.com/commits).
+
+Please *do not* base FoundIn- on the Chrome version number provided in the
+original report. This is often based on the version number the individual is
+using when discovering this issue or is automatically set in the report by the
+tracker’s report wizard and is not correct in terms of coverage of all active
+release channels.
+
+For V8 bugs, you can set `FoundIn-<current extended stable>` unless you have
+reproduced the issue or an accurate bisection has been provided.
+(See [Assign, below](#Assign).)
+
+### Set impacted operating systems
+
+Set the `OS` field as best you can based on [these guidelines](security-labels.md#OS-Labels).
+You do not need to reproduce the bug on each platform, but it really helps if
+you set this field roughly right to ensure the bug has the attention of the
+different desktop and mobile release teams.
+
+Some issues may be specific to a particular platform, if you need to reproduce a
+bug that is platform specific and you do not have access to a device with that
+OS, please [ask for help](#Ask-for-help), there is likely someone on the team
+that does and can help you.
+
+ChromeOS is in a separate issue tracker. VRP reports for ChromeOS should be
+[directly reported to ChromeOS](https://bughunters.google.com/report). Please
+request the reporter submit direct to ChromeOS via that reporting route to
+ensure it is received by the appropriate team.
+
+### Assign
+
+Security bugs are not automatically visible, so you must add people to get them
+fixed. For each bug, set:
+
+* The **component** – due to a limited set of auto-cc rules, this may add some
+  visibility.
+* An **assignee/owner**. Use `git blame` or look for similar past bugs in the
+  tracker.
+* Lots of **cc**s. Copy everyone who could possibly be relevant. Use the owners
+  file for a particular feature to help achieve this.
+* Add a **comment** so that recipients know what’s expected, and why you think
+  they’re the right person to take action.
+  * Be sure to convey if you have reproduced this issue and your determinations
+  about security relevance or diagnosis.
+
+It’s okay if you cannot determine or  know the exact right assignee, but please
+pass it along to / include someone who can direct it more precisely.
+
+*Some types of bugs have specific assignment needs:*
 * **V8 bugs**. First, [upload benign-looking test cases to
   ClusterFuzz](clusterfuzz-for-shepherds.md) if it isn't already
   there (please keep an eye out for any special flags and debug vs release).
@@ -243,142 +346,6 @@
   mfoltz@chromium.org. They are also working on holistic solutions to improving
   the security of fullscreen, so please remember to look for potential
   duplicates of ongoing work.
-
-Note that **even when you are handing off triage to another team or point of
-contact**, it is your responsibility to ensure that the `Security_Severity` and
-`FoundIn` fields are set as soon as possible (and definitely before the end of
-your shepherding shift). Work with your point of contact to set these. For
-instance, you may want to set initial/provisional values for these fields and
-ask them whether it matches their understanding.
-
-Tips for reproducing bugs:
-
-* Assume that test cases may be malicious. You should only reproduce bugs
-  on your local machine if you're completely certain that you understand
-  100% of the test case. If not, use a disposable virtual machine. If you're
-  inside Google, a good way to do this is using
-  [Redshell](https://goto.google.com/redshell-for-chrome-shepherds).
-* For any sort of a crash, CHECK/DCHECK or memory safety problem
-  [use ClusterFuzz](clusterfuzz-for-shepherds.md). As well as reproducing bugs,
-  ClusterFuzz will help you with lots of subsequent bisection and labelling
-  tasks. Currently ClusterFuzz cannot guard against malicious test cases,
-  so be just as paranoid as if you were running a test case locally.
-* [Instructions for using an Android emulator can be found
-  here](/docs/android_emulator.md). If you're inside Google, we have a
-  [guide for testing using Google infrastructure](https://goto.google.com/android-for-chrome-shepherds).
-* When you can't just build from a specific branch locally, check out
-  [https://dev.chromium.org/getting-involved/dev-channel](https://dev.chromium.org/getting-involved/dev-channel)
-  or
-  [https://commondatastorage.googleapis.com/chromium-browser-asan/index.html](https://commondatastorage.googleapis.com/chromium-browser-asan/index.html)
-  for the latest release of a specific version.
-* There are many tools available to help you reproduce various memory issues
-  reliably. If you aren't already familiar with them, check out
-  [AddressSanitizer](https://www.chromium.org/developers/testing/addresssanitizer),
-  [MemorySanitizer](https://www.chromium.org/developers/testing/memorysanitizer),
-  [ThreadSanitizer](https://www.chromium.org/developers/testing/threadsanitizer-tsan-v2),
-  and
-  [UndefinedBehaviorSanitizer](https://www.chromium.org/developers/testing/undefinedbehaviorsanitizer).
-* The [get_asan_chrome](https://source.chromium.org/chromium/chromium/src/+/main:tools/get_asan_chrome/get_asan_chrome.py)
-  helper script is a handy way to download ASAN Chrome. The --help flag
-  provides usage instructions, e.g. to fetch builds for various versions and
-  platforms.
-* If you run into issues with a reproducible ClusterFuzz test case (like
-  missing symbols, or if anything else seems off), try uploading the test case
-  again using a different job type with a more mature tool (e.g. ASan on Linux).
-  It may give more complete information.
-
-#### Step 2. Assess the severity.
-
-[See the severity guidelines](severity-guidelines.md). If it's a critical
-vulnerability, act quick! We aim to get users patched in < 30 days. Remember
-that if something requires an unusual configuration or complicated user
-interaction, the severity rating should be lowered.
-
-Bug chains are typically composed of several individual security bugs and
-should be split into a new bug for each potential fix required, so this allows
-each team to work on fixing their part of the chain. In cases like this, leave
-the main bug as the severity/priority of the full chain, and mark child bugs as
-being blockers of the parent bug each with their own separate severity. Each
-child bug can have its own priority. Examples of this in action are [issue
-352369](https://crbug.com/352369) and [issue 453937](https://crbug.com/453937).
-
-Even after initial triage, re-assess the severity while you're looking at a
-security bug update: does it have new information in the bug that could change
-the assessment? Be especially on the lookout for Highs that are really
-Criticals, and Lows that are really Mediums (make sure to account for process
-types and sandbox boundaries).
-
-For V8 issues, it can be hard to identify the correct security severity.
-Always set the severity to High unless there's strong evidence of an obvious
-mitigation. Please add the `Security_Needs_Attention-Severity` label alongside
-the regular `Security_Severity-*` label. If the bug is not exploitable, or is
-mitigated, the V8 team will reduce the security severity (to avoid unnecessary
-risk of merging the bug into stable branches).
-
-If an issue is found that can't affect any users running a default configuration
-of Chrome (e.g. an issue in code guarded by a command-line flag that is off by
-default), the `Security_Severity-*` label should still be set as if the issue
-is affecting users running a default configuration of Chrome (but see the next
-section about `FoundIn` and `Security_Impact-None`).
-
-#### Step 3. Set FoundIn
-
-Identify the earliest affected branch (Extended Stable, Stable, Beta or Head)
-and set the corresponding `FoundIn` label (for example `FoundIn-66` if the
-extended stable milestone is 66 and you've confirmed it's reproducible on M66).
-If you reproduced the bug with ClusterFuzz, it should do this on your behalf.
-
-If you performed a bisection or were provided one with the commit that
-introduced the problem, you can check which milestone has that commit by
-navigating to https://chromiumdash.appspot.com/commit/COMMIT_HASH_HERE.
-
-Sometimes Extended Stable is the same milestone as Stable; sometimes it
-differs. If in doubt about the currently active milestones, check
-[ChromiumDash](https://chromiumdash.appspot.com/releases?platform=Windows).
-(It's fine to just check the Windows platform, via that link - there's no need
-to look at all the different platforms). There's no need to check for
-reproducibility on milestones earlier than the current Stable milestone.
-
-If an issue is found that can't affect any users running a default configuration
-of Chrome (e.g. an issue in code guarded by a command-line flag that is off by
-default), then do not set the `FoundIn` label; instead, set the impact to
-`Security_Impact-None` (but see
-[here](security-labels.md#when-to-use-security_impact_none-toc_security_impact_none)
-for additional nuances around using `Security_Impact-None`).
-
-#### Step 4. [Check other labels](security-labels.md).
-
-Much of Chrome's development and release process depends on bugs having the
-right labels and components. Labels and components are vitally important for
-merging the fix to the right releases, and ensuring reporters are credited
-correctly. They also help with metrics and visibility.
-
-Labels to **double-check** (the first two should already be there if the bug
-was filed using the Security template):
-
-* **Restrict-View-SecurityTeam**
-* **Type-Bug-Security**
-* If you want to prevent the bug from becoming unrestricted after it has been
-  closed, add **Restrict-View-SecurityEmbargo**. This should be done if the
-  reporter wishes to remain anonymous, if the description or comments contain
-  PII, or if the bug contains malware samples.
-* **Security_Severity** - your responsibility as Shepherd.
-* **FoundIn** - your responsibility as Shepherd.
-* **reward_to** - if the bug was filed internally on behalf of somebody
-  external (for instance, a @chromium.org email reporting "I'm filing this on
-  behalf of" and the like). This is also very important; please check.
-
-You can expect Sheriffbot to fill in lots of other labels; for example,
-the `M-` label to indicate the target milestone. It's best to allow
-Sheriffbot to add the rest, as its rules have congealed from years of
-accumulated security wisdom. See
-[the Security Labels document](security-labels.md) for an explanation of what
-the labels mean.
-
-**If you change anything, add a comment which explains any status
-changes.** Severity, milestone, and priority assignment generally require
-explanatory text.
-
 * Report suspected malicious URLs to SafeBrowsing:
   * Public URLs:
     * [Report malware](https://safebrowsing.google.com/safebrowsing/report_badware/?hl=en)
@@ -387,105 +354,181 @@
   * Googlers: see instructions at [go/safebrowsing-escalation](https://goto.google.com/safebrowsing-escalation)
   * Report suspected malicious file attachments to SafeBrowsing.
 * Make sure the report is properly forwarded when the vulnerability is in an
-  upstream project, the OS, or some other dependency.
+  **upstream project**, the OS, or some other dependency.
 * For vulnerabilities in services Chrome uses (e.g. Omaha, Chrome Web Store,
   SafeBrowsing), make sure the affected team is informed and has access to the
   necessary bugs.
+* Chrome for iOS - bugs suspected to be in **WebKit**:
+    * Reproduce using an iOS device or desktop Safari.
+    * Assign severity, impact, and component labels.
+    * If the issue is in Webkit
+      * Label `ExternalDependency` and `Hotlist-WebKit`. This label is monitored
+      by Apple.
+      * If reported by an external VRP reporter, request they report the issue
+      directly to Webkit and provide us the WebKit issue ID after they have done
+      so.
+      * If this is an internally discovered issue, file a security bug in the
+      Security product at [bugs.webkit.org](https://bugs.webkit.org) and
+      cc:chrome-ios-security-bugs@google.com. This alias is monitored by the iOS
+      Chrome team so they can be notified when the WebKit bug is fixed.
+        * Note the WebKit bug ID in the Chromium issue report.
+    * All security issues need owners, the WebKit ones can be assigned to ajuma@.
 
-##### Labeling For Chrome On iOS
+### Shift handoff
 
-* Reproduce using iOS device or desktop Safari.
-* Assign severity, impact, and component labels.
-* If the issue is in WebKit:
-  * Label **ExternalDependency**.
-  * Label **Hotlist-WebKit**. This label is monitored by Apple friends.
-  * Do not fill **FoundIn**. It is applicable to Chrome versions and we can't
-    backmerge WebKit fixes.
-  * File a security bug at [bugs.webkit.org](https://bugs.webkit.org), and CC
-    chrome-ios-security-bugs@google.com. This alias is monitored by the iOS
-    Chrome team so they can be notified when the WebKit bug is fixed. Even
-    better: suggest the reporter to do it themselves and CC you. This way they
-    are getting a better visibility and credit.
-  * Note the WebKit bug ID in the crbug report.
-  * All security issues need owners, the WebKit ones can be assigned to ajuma@.
+As you work through the queue each day, please manage your time and ensure you
+have addressed all red rows and cells in the sheet to the best of your ability.
+Make sure there are no red cells at the top of your sheet before the end of your
+shift. It’s not okay to leave a backlog for the next oncoming security shepherd.
 
-### Find An Owner To Fix The Bug
+Please fill out the [Shepherding Handoff
+Log](https://goto.google.com/chrome-security-shepherd-handoff) to communicate
+issues from your shift that may be helpful to the oncoming shift.
 
-That owner can be you! Otherwise, this is one of the more grey areas of
-shepherding. With experience, you'll figure out good goto people for certain
-areas. Until then, here are some tips.
+### Ask for help
 
-**Determine the correct component before continuing.** It's not enough on its
-own, but it's a good starting point. Many components will automatically apply
-some CCs who may be able to help you out. If it's a crash bug, see if
-ClusterFuzz is able to provide one (will appear in the same card as the culprit
-CL). You can also use `git hyper-blame` and check OWNERS files to see who might
-own the relevant code.
+Security bug triage is hard. We receive around 75 bug reports per week on
+average. **If you are ever stuck or in doubt**, please ask for help from the
+[Chrome Security Shepherds chat](https://goto.google.com/chrome-security-shepherds-chat)
+or the [Chrome Security Chat](https://goto.google.com/chrome-security-chat).
+During some shifts, there are just too many incoming bugs. It’s okay to ask for
+help, please do!
 
-**For crashes, check to see if ClusterFuzz provides a culprit CL.** Before you
-assign a bug based on this, do a quick sanity check to ensure the CL could have
-caused the bug. If the result seems wrong, apply the Test-Predator-Wrong label
-to the bug and keep going.
+You may also like the classic [HOWTO: Be a Security Shepherd deck](https://docs.google.com/presentation/d/1eISJXxyv7dUCGUKk_rvUI9t9s2xb98QY4d_-dZSa7Wg/edit#slide=id.p)
 
-If you're able to narrow this to a specific regression range, usually from
-ClusterFuzz for crash bugs, do a quick pass over the git log to see if any CLs
-stand out. If you aren't sure, don't be afraid to add CCs to the bug and ask!
+Because shepherding is fun. You like fun. Don't you? Fun is great.
 
-At this point, you'll probably need to dive in and attempt to root cause the
-bug, which is another complicated grey area that you'll figure out with
-experience. Try not to spend too much time on this for any given bug, as some
-cases will simply be too difficult without a deep understanding of certain
-portions of the codebase.
+## Secondary Shepherd
 
-* If you can narrow the bug to a specific file or block of code, or if something
-  stands out as suspicious, try to assign an owner based on `git hyper-blame` or
-  add some CCs based on OWNERS files.
-* If not, consider searching in the issue tracker for people that fixed similar
-  bugs or bugs in similar areas of the code base, such as issues with the same
-  components, recently. For example, let's say you were trying to figure out a
-  good person to assign a Content>Fonts issue. Look for `status=fixed,verified`
-  and query by when the issues were closed after (i.e. w/ in the last 30 days ==
-  `closed>today-30`).
+### Check in on triaged issues
 
-Got stuck? Ask #chrome-security or someone from
-[go/chrome-security-sheriff-mentors](https://goto.google.com/chrome-security-sheriff-mentors)
-for help! That's why we're here. Don't be afraid to do this!
+Review open security bug reports and check that progress is occurring. This does
+not apply to the new bug reports as these are handled by the primary shepherd.
+The rule of thumb is *if there is any red cell on the dashboard, it needs your
+attention*: that especially includes the `last updated` column. Our [severity
+guidelines](severity-guidelines.md) contain the expected duration for shipping
+fixes, but it’s important to remember that to get a fix to all users in 60 days
+or so, this may require us to land a fix in a week or two.
 
-Make sure that the person you assign to handle a bug is not OOO. And, generally,
-explicitly CC more than one person on the bug, if possible, and preferably
-people from more than one geographic region. (See the OWNERS file(s) that
-affect(s) the relevant area of code.)
+*Suggestions for cultivating progress on security bugs:*
+* Don’t just add a comment to the bug as these can disappear into spam (though a
+  well-crafted, meaningful, actionable comment can be effective).
+* Contact the owner via chat or email in addition to commenting on the bug (so
+  others on the bug can see an update is needed).
+* cc: more relevant people
+* Think about what you can do to unblock the bug. What would _you_ do next?
+ Perhaps you bring in a subject matter expert of some aspect of the bug that is
+ a particular sticking point or suggest a different approach to reproduce the
+ bug. Sometimes a security perspective can help shed light on a different way
+ forward.
+* Are there old, open `Security_Impact-None` bugs in unlaunched features, where
+  the response has been that there are no plans to launch that feature? Perhaps
+  inquire as to if that code can be removed rather than keeping vulnerable code
+  production code base. (Removing code that is not being used is a win!)
+* Consider if it is better for you to make meaningful steps forward on three
+  bugs versus simple pings on many bugs.
 
-**Sometimes, finding an owner isn't enough to ensure that a bug will get
-fixed.** Check the stale bug list on the security dashboard and try resolve
-some of the problems that might be blocking these issues. If you get in touch
-with a bug owner off of the issue tracker, be sure to have them update the bug
-so that future shepherds are aware of the status.
+You can’t possibly usher all bugs toward meaningful progress during your shift.
+As a general rule, expect to spend a solid ten hours ushering bugs toward
+progress during your shift. Use the `last updated` column to avoid duplicating
+the work of the previous secondary.
 
-> Q: Why isn’t setting the component alone good enough?
->
-> A: CCs are critical because just assigning to a component is ineffective
-> because the component’s team cannot see the issues unless they have the
-> Security View permissions.
+### Handle incoming security emails
 
-### Using The Permission API Kill Switch
+Ensure that all incoming inquiries to the [security@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/security),
+[security-dev@chromium.org](https://groups.google.com/a/chromium.org/forum/#!forum/security-dev),
+and
+[chrome-security@google.com](https://groups.google.com/a/google.com/forum/#!forum/chrome-security)
+lists get a reply (by someone; not necessarily you). See
+[go/chrome-security-emails](https://goto.google.com/chrome-security-emails)
+for a dashboard.
 
-If you find a vulnerability in a Permission API and need to use the Global
-Permissions Kill Switch, then follow [the
-instructions](https://docs.google.com/document/d/17JeYt3c1GgghYoxy4NKJnlxrteAX8F4x-MAzTeXqP4U)
+* When triaging an email to be handled off of the list, make sure to bcc: the
+list that it arrived on, so that others (including future secondary shepherds)
+can see that it has been handled.
+* Some of these emails are requests for the inclusion of third-party code.
+By the time you do shift handoff, please ensure these are either completed or
+have been acknowledged by some other owner. If not, you may need to do them
+yourself.
+  * Please see [How to do Chrome Third-Party Security Reviews](https://goto.google.com/how-to-do-chrome-third-party-security-reviews) for tips.
 
-### Vulnerabilities in Chromium's dependencies
+## Other Helpful Info
 
-If you are handling a bug in one of Chromium's dependencies, see
-[go/howto-investigate-autovm-chromium-bugs](https://goto.google.com/howto-investigate-autovm-chromium-bugs)
-(Google-only, sorry) for some hints.
+### What do all these bug labels mean?
 
-### Wrapping Up The Fixed Issue
+[Security Labels](security-labels.md).
 
-1. Check with the developer that the issue can be closed as Fixed to allow
-   Sheriffbot to add the appropriate merge-review labels based on
-   Security_Severity and Security_Impact.
+### A bug owner is asking for security input on backporting a fix on a security
+bug I’ve shepherded. What do I do here?
 
-## End Of Rotation
+You are not responsible for handling merges or approving a fix for backmerge.
+If the issue is resolved and there is a landed CL, please ensure the bug is
+closed as Fixed. Please also make sure the bug has a severity and FoundIn set.
+This will allow the bot (Sheriffbot) to add the appropriate merge review labels
+(based on rules driven by severity and security_impact, derived from FoundIn).
+See [security merge triage](../process/merge_request.md#Security-merge-triage)
+for more information.
 
-Update the [Shepherd Handoff Log](http://go/chrome-security-shepherd-handoff).
+That issue will be visible to the security merge review queue. There are
+designated members of the security team who have the hefty responsibility of
+reviewing security issues for backmerge. Merge approvals will be handled by them
+after at least the fix has had sufficient bake time on Canary.
+
+### When / how does X happen to a security bug?
+
+(e.g. how and when does a VRP bug get to the Chrome VRP Panel?)
+[See Life of a Security Issue](life-of-a-security-issue.md).
+
+### I have questions related to Chrome VRP policy and scope.
+
+[Chrome VRP policies and rewards page](https://g.co/chrome/vrp). You can also
+reach out directly to the Chrome VRP TL or ask questions in the
+[Chrome Security Shepherds chat](http://go/chrome-security-shepherds-chat), all
+VRP Panel members are also members of that chat.
+
+### There is PII or other data in this report we may not want to publicly
+disclose (either now or in the future).
+
+For these cases, please add the `Restrict-View-SecurityEmbargo` label to the
+report. For cases of PII that can’t be permanently deleted for the report, this
+label should remain indefinitely.
+
+For cases in which we are just delaying public disclosure (such as when a
+security issue impacts other products or vendors), please use this label and set
+a next-action date so that disclosure can be re-evaluated at that time.
+
+### Protecting researcher identities
+
+Many researchers report security issues under a pseudonym and from a specific
+email address pertaining to that pseudonym. Please do not refer to the
+researcher by the email username directly in any comments of the report.
+When reports are publicly disclosed, that becomes visible to all and we have to
+delete those comments to protect that information. To direct a comment at an
+external security researcher, please use “OP”, “reporter”, or "researcher”.
+
+### Shepherding Scheduling
+
+* [Current Shepherds](http://go/whos-the-shepherd)
+* [Rotation schedule](https://docs.google.com/spreadsheets/d/10sLYZbi6QfLcXrhO-j5eSc82uc7NKnBz_o1pR9y8h7U/edit#gid=0)
+* If you're a Shepherd, you should get a calendar invite.
+  Please accept it to acknowledge your upcoming shepherding duty.
+* If you need to swap shifts, ask around for a volunteer and then just update
+  the [rotation sheet](https://docs.google.com/spreadsheets/d/10sLYZbi6QfLcXrhO-j5eSc82uc7NKnBz_o1pR9y8h7U/edit#gid=0) and wait 10 minutes for the calendar invites to be updated.
+
+### Incident response
+
+Sometimes you’ll need to handle a security emergency, such as a critical
+severity bug or bug known or under active exploitation in the wild. In such
+cases:
+* As soon as possible, reach out the Shepherds chat for a Chrome Security
+  Incident Responder, so they can take on IR Commander responsibilities.
+* Sometimes features can be switched off using feature flags – for example
+  [in permissions] ((https://docs.google.com/document/d/17JeYt3c1GgghYoxy4NKJnlxrteAX8F4x-MAzTeXqP4U).
+  Check with the engineer if that is a possibility in the case of this issue.
+
+
+That's a lot of stuff! You have this resource and your peers to lean on
+for questions and expertise. Hopefully this doc helps.
+You're gonna do great!
+
+
diff --git a/extensions/browser/api/declarative_net_request/constants.cc b/extensions/browser/api/declarative_net_request/constants.cc
index eb90b1a1..674acd8 100644
--- a/extensions/browser/api/declarative_net_request/constants.cc
+++ b/extensions/browser/api/declarative_net_request/constants.cc
@@ -56,8 +56,10 @@
     "\"resourceTypes\" key. It may only include the \"main_frame\" and "
     "\"sub_frame\" resource types.";
 const char kErrorRegexTooLarge[] =
-    "Rule with id * specified a more complex regex than allowed as part of the "
-    "\"*\" key.";
+    "Rule with id * was skipped as the \"*\" value exceeded the 2KB memory "
+    "limit when compiled. Learn more: "
+    "https://developer.chrome.com/docs/extensions/reference/api/"
+    "declarativeNetRequest#regex-rules";
 const char kErrorNoHeaderListsSpecified[] =
     "Rule with id * does not specify a value for \"*\" or \"*\" key. At least "
     "one of these keys must be specified with a non-empty list.";
diff --git a/extensions/browser/api/declarative_net_request/constants.h b/extensions/browser/api/declarative_net_request/constants.h
index cbe2f5d..b0ab7fff 100644
--- a/extensions/browser/api/declarative_net_request/constants.h
+++ b/extensions/browser/api/declarative_net_request/constants.h
@@ -264,6 +264,10 @@
 // The per-extension maximum amount of disabled static rules.
 inline constexpr int kMaxDisabledStaticRules = 5000;
 
+// Maximum size of a compiled RegEx rule in KB. Limited to 2 KB which means
+// that given 1024 rules, the total usage would be 2 MB.
+inline constexpr int kRegexMaxMemKb = 2;
+
 // Identifier for a Flatbuffer containing `flat::EmbedderConditions` as the
 // root.
 extern const char kEmbedderConditionsBufferIdentifier[];
diff --git a/extensions/browser/api/declarative_net_request/utils.cc b/extensions/browser/api/declarative_net_request/utils.cc
index 7363570..87320283 100644
--- a/extensions/browser/api/declarative_net_request/utils.cc
+++ b/extensions/browser/api/declarative_net_request/utils.cc
@@ -317,9 +317,8 @@
 
   options.set_log_errors(false);
 
-  // Limit the maximum memory per regex to 2 Kb. This means given 1024 rules,
-  // the total usage would be 2 Mb.
-  options.set_max_mem(2 << 10);
+  // Limit the maximum memory per regex.
+  options.set_max_mem(kRegexMaxMemKb << 10);
 
   return options;
 }
diff --git a/extensions/components/native_app_window/native_app_window_views.cc b/extensions/components/native_app_window/native_app_window_views.cc
index 00087f2..40b1543 100644
--- a/extensions/components/native_app_window/native_app_window_views.cc
+++ b/extensions/components/native_app_window/native_app_window_views.cc
@@ -397,7 +397,7 @@
 }
 
 bool NativeAppWindowViews::CanHaveAlphaEnabled() const {
-  return widget_->IsTranslucentWindowOpacitySupported();
+  return views::Widget::IsWindowCompositingSupported();
 }
 
 void NativeAppWindowViews::SetVisibleOnAllWorkspaces(bool always_visible) {
diff --git a/fuchsia_web/runners/cast/cast_runner.cml b/fuchsia_web/runners/cast/cast_runner.cml
index f29f9a5b..59946c3e 100644
--- a/fuchsia_web/runners/cast/cast_runner.cml
+++ b/fuchsia_web/runners/cast/cast_runner.cml
@@ -66,7 +66,7 @@
     {
       directory: "tzdata-icu",
       rights: [ "r*" ],
-      path: "/config/tzdata",
+      path: "/config/tzdata/icu",
     },
     {
       // Capabilities used by the Runner itself.
diff --git a/fuchsia_web/runners/cast/cast_runner_integration_test.shard.test-cml b/fuchsia_web/runners/cast/cast_runner_integration_test.shard.test-cml
index 0db1f458..81ac1c3 100644
--- a/fuchsia_web/runners/cast/cast_runner_integration_test.shard.test-cml
+++ b/fuchsia_web/runners/cast/cast_runner_integration_test.shard.test-cml
@@ -5,7 +5,7 @@
   offer: [
     {
       directory: [
-		"config-data",
+        "config-data",
         "tzdata-icu",
       ],
       from: "parent",
diff --git a/fuchsia_web/webengine/browser/frame_permission_controller.cc b/fuchsia_web/webengine/browser/frame_permission_controller.cc
index 5d0c4e7f..b4d5a30f 100644
--- a/fuchsia_web/webengine/browser/frame_permission_controller.cc
+++ b/fuchsia_web/webengine/browser/frame_permission_controller.cc
@@ -114,7 +114,6 @@
 void FramePermissionController::RequestPermissions(
     const std::vector<PermissionType>& permissions,
     const url::Origin& requesting_origin,
-    bool user_gesture,
     base::OnceCallback<void(const std::vector<PermissionStatus>&)> callback) {
   std::vector<PermissionStatus> result;
   result.reserve(permissions.size());
diff --git a/fuchsia_web/webengine/browser/frame_permission_controller.h b/fuchsia_web/webengine/browser/frame_permission_controller.h
index 3e89cce8..8280cfad 100644
--- a/fuchsia_web/webengine/browser/frame_permission_controller.h
+++ b/fuchsia_web/webengine/browser/frame_permission_controller.h
@@ -61,7 +61,6 @@
   void RequestPermissions(
       const std::vector<blink::PermissionType>& permissions,
       const url::Origin& requesting_origin,
-      bool user_gesture,
       base::OnceCallback<
           void(const std::vector<blink::mojom::PermissionStatus>&)> callback);
 
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
index e3a60fe..7541de8cb 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
@@ -28,7 +28,7 @@
   frame->permission_controller()->RequestPermissions(
       request_description.permissions,
       url::Origin::Create(request_description.requesting_origin),
-      request_description.user_gesture, std::move(callback));
+      std::move(callback));
 }
 
 void WebEnginePermissionDelegate::ResetPermission(
@@ -50,7 +50,7 @@
   frame->permission_controller()->RequestPermissions(
       request_description.permissions,
       render_frame_host->GetLastCommittedOrigin(),
-      request_description.user_gesture, std::move(callback));
+      std::move(callback));
 }
 
 blink::mojom::PermissionStatus WebEnginePermissionDelegate::GetPermissionStatus(
diff --git a/infra/config/generated/builders/try/lacros-amd64-generic-rel-compilator/properties.json b/infra/config/generated/builders/try/lacros-amd64-generic-rel-compilator/properties.json
index 26e70ba9..f9b70e39 100644
--- a/infra/config/generated/builders/try/lacros-amd64-generic-rel-compilator/properties.json
+++ b/infra/config/generated/builders/try/lacros-amd64-generic-rel-compilator/properties.json
@@ -52,10 +52,6 @@
       ]
     }
   },
-  "$build/flakiness": {
-    "check_for_flakiness": true,
-    "check_for_flakiness_with_resultdb": true
-  },
   "$build/reclient": {
     "instance": "rbe-chromium-untrusted",
     "jobs": 500,
diff --git a/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json b/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
index 93d9d96..2dde72b5 100644
--- a/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
+++ b/infra/config/generated/builders/try/lacros-amd64-generic-rel/properties.json
@@ -56,10 +56,6 @@
       ]
     }
   },
-  "$build/flakiness": {
-    "check_for_flakiness": true,
-    "check_for_flakiness_with_resultdb": true
-  },
   "$recipe_engine/resultdb/test_presentation": {
     "column_keys": [],
     "grouping_keys": [
@@ -68,6 +64,5 @@
     ]
   },
   "builder_group": "tryserver.chromium.chromiumos",
-  "cq": "required",
   "recipe": "chromium/orchestrator"
 }
\ No newline at end of file
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index 22f12a6..9d028f7 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -49,8 +49,6 @@
 
 * [ios-simulator](https://ci.chromium.org/p/chromium/builders/try/ios-simulator) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""ios-simulator""))
 
-* [lacros-amd64-generic-rel](https://ci.chromium.org/p/chromium/builders/try/lacros-amd64-generic-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""lacros-amd64-generic-rel""))
-
 * [lacros-arm-generic-rel](https://ci.chromium.org/p/chromium/builders/try/lacros-arm-generic-rel) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""lacros-arm-generic-rel""))
 
 * [linux-chromeos-compile-dbg](https://ci.chromium.org/p/chromium/builders/try/linux-chromeos-compile-dbg) ([definition](https://cs.chromium.org/search?q=+file:/try/.*\.star$+""linux-chromeos-compile-dbg""))
diff --git a/infra/config/generated/cq-usage/default.cfg b/infra/config/generated/cq-usage/default.cfg
index 91c3df3b..03f39871 100644
--- a/infra/config/generated/cq-usage/default.cfg
+++ b/infra/config/generated/cq-usage/default.cfg
@@ -62,9 +62,6 @@
         name: "chromium/try/ios-simulator"
       }
       builders {
-        name: "chromium/try/lacros-amd64-generic-rel"
-      }
-      builders {
         name: "chromium/try/lacros-arm-generic-rel"
       }
       builders {
diff --git a/infra/config/generated/cq-usage/full.cfg b/infra/config/generated/cq-usage/full.cfg
index 59391b9..e08f17d 100644
--- a/infra/config/generated/cq-usage/full.cfg
+++ b/infra/config/generated/cq-usage/full.cfg
@@ -1752,26 +1752,6 @@
         }
       }
       builders {
-        name: "chromium/try/lacros-amd64-generic-rel"
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "docs/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/generated/builders/try/lacros-amd64-generic-rel/.+"
-        }
-      }
-      builders {
         name: "chromium/try/lacros-arm-generic-rel"
         location_filters {
           gerrit_host_regexp: ".*"
diff --git a/infra/config/generated/luci/commit-queue.cfg b/infra/config/generated/luci/commit-queue.cfg
index 7e60353..60b0ab5 100644
--- a/infra/config/generated/luci/commit-queue.cfg
+++ b/infra/config/generated/luci/commit-queue.cfg
@@ -3085,25 +3085,7 @@
       }
       builders {
         name: "chromium/try/lacros-amd64-generic-rel"
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "docs/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/.+"
-          exclude: true
-        }
-        location_filters {
-          gerrit_host_regexp: ".*"
-          gerrit_project_regexp: ".*"
-          path_regexp: "infra/config/generated/builders/try/lacros-amd64-generic-rel/.+"
-        }
-        mode_allowlist: "DRY_RUN"
-        mode_allowlist: "FULL_RUN"
+        includable_only: true
       }
       builders {
         name: "chromium/try/lacros-amd64-generic-rel-compilator"
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index a98ad24..11758f7 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -18742,7 +18742,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:Mac Builder Next"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-13"
+      dimensions: "os:Mac-14"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
@@ -82222,7 +82222,6 @@
         '    }'
         '  },'
         '  "builder_group": "tryserver.chromium.chromiumos",'
-        '  "cq": "required",'
         '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "chromium/orchestrator"'
         '}'
@@ -93829,7 +93828,7 @@
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-builder-next"
       dimensions: "cpu:arm64"
-      dimensions: "os:Mac-13"
+      dimensions: "os:Mac-14"
       dimensions: "pool:luci.chromium.try"
       dimensions: "ssd:1"
       exe {
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index fc0e5a4..e0c9dde8 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -83,6 +83,7 @@
     MAC_14 = os_enum(os_category.MAC, "Mac-14"),
     MAC_DEFAULT = os_enum(os_category.MAC, "Mac-13"),
     MAC_ANY = os_enum(os_category.MAC, "Mac"),
+    MAC_BETA = os_enum(os_category.MAC, "Mac-14"),
     WINDOWS_10 = os_enum(os_category.WINDOWS, "Windows-10"),
     WINDOWS_11 = os_enum(os_category.WINDOWS, "Windows-11"),
     WINDOWS_DEFAULT = os_enum(os_category.WINDOWS, "Windows-10"),
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 341304b..e86f3d0 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -2522,7 +2522,7 @@
         ],
     ),
     cores = None,
-    os = os.MAC_13,
+    os = os.MAC_BETA,
     cpu = cpu.ARM64,
     console_view_entry = consoles.console_view_entry(
         category = "mac",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
index b34c072..ce94d42f 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
@@ -298,7 +298,6 @@
     compilator = "lacros-amd64-generic-rel-compilator",
     contact_team_email = "chrome-desktop-engprod@google.com",
     main_list_view = "try",
-    tryjob = try_.job(),
 )
 
 try_.compilator_builder(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 7da31bb..ebdedcb 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -119,7 +119,7 @@
     mirrors = ["ci/Mac Builder Next"],
     gn_args = "ci/Mac Builder Next",
     builderless = False,
-    os = os.MAC_13,
+    os = os.MAC_BETA,
     cpu = cpu.ARM64,
     reclient_jobs = reclient.jobs.HIGH_JOBS_FOR_CQ,
 )
diff --git a/internal b/internal
index 4baa1ab..7bc0f56 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 4baa1abaa62ee7bde3412e8dbef614134978edab
+Subproject commit 7bc0f561fe056c19918d9c9efef049525389066e
diff --git a/ios/chrome/app/strings/ios_strings.grd b/ios/chrome/app/strings/ios_strings.grd
index bfcc7f1..9c6d2b7 100644
--- a/ios/chrome/app/strings/ios_strings.grd
+++ b/ios/chrome/app/strings/ios_strings.grd
@@ -3258,10 +3258,10 @@
         To stop others from using your password, open the <ph name="APP">$1<ex>Netflix</ex></ph> app to change your password
       </message>
       <message name="IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE" desc="Subtitle text on the sharing status view when it succeeds describing that the recipient can now use the shared password. [iOS only]">
-        <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="USERNAME">$1<ex>johndoe</ex></ph><ph name="END_BOLD">END_BOLD</ph> can now use your username and password when they use Google Password Manager to sign in to <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="WEBSITE">$2<ex>amazon.com</ex></ph><ph name="END_BOLD">END_BOLD</ph>. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn more<ph name="END_LINK">END_LINK</ph>
+        <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="USERNAME">$1<ex>johndoe</ex></ph><ph name="END_BOLD">END_BOLD</ph> can now use your username and password when they use Google Password Manager to sign in to <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="WEBSITE">$2<ex>amazon.com</ex></ph><ph name="END_BOLD">END_BOLD</ph>.
       </message>
       <message name="IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE_MULTIPLE_RECIPIENTS" desc="Subtitle text on the sharing status view when it succeeds describing that the recipient can now use the shared password. [iOS only]">
-        Your family members can now use your username and password when they use Google Password Manager to sign in to <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="WEBSITE">$1<ex>amazon.com</ex></ph><ph name="END_BOLD">END_BOLD</ph>. <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn more<ph name="END_LINK">END_LINK</ph>
+        Your family members can now use your username and password when they use Google Password Manager to sign in to <ph name="BEGIN_BOLD">BEGIN_BOLD</ph><ph name="WEBSITE">$1<ex>amazon.com</ex></ph><ph name="END_BOLD">END_BOLD</ph>.
       </message>
       <message name="IDS_IOS_PASSWORD_SHARING_SUCCESS_TITLE" desc="Title text on the sharing status view when it succeeded. [iOS only]">
         Password Shared
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE.png.sha1
index 164b3820..a5f2707 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE.png.sha1
@@ -1 +1 @@
-7c96c422a707b02e20b426c9dee51cedd3087112
\ No newline at end of file
+35c3acafc9c9173b0747eef6b7d906b1216a4b27
\ No newline at end of file
diff --git a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE_MULTIPLE_RECIPIENTS.png.sha1 b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE_MULTIPLE_RECIPIENTS.png.sha1
index 4d29693..c14ab9fa 100644
--- a/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE_MULTIPLE_RECIPIENTS.png.sha1
+++ b/ios/chrome/app/strings/ios_strings_grd/IDS_IOS_PASSWORD_SHARING_SUCCESS_SUBTITLE_MULTIPLE_RECIPIENTS.png.sha1
@@ -1 +1 @@
-68938358a1758a083cfcc6a3449ec30fa8ade495
\ No newline at end of file
+9fd8e05a6ba776106da214426b6387509baa9fe6
\ No newline at end of file
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index c448ed52..b1f1d83 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1405,10 +1405,6 @@
      flag_descriptions::kAppStoreRatingLoosenedTriggersName,
      flag_descriptions::kAppStoreRatingLoosenedTriggersDescription,
      flags_ui::kOsIos, FEATURE_VALUE_TYPE(kAppStoreRatingLoosenedTriggers)},
-    {"ios-password-auth-on-entry",
-     flag_descriptions::kIOSPasswordAuthOnEntryName,
-     flag_descriptions::kIOSPasswordAuthOnEntryDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(password_manager::features::kIOSPasswordAuthOnEntry)},
     {"tab-resumption", flag_descriptions::kTabResumptionName,
      flag_descriptions::kTabResumptionDescription, flags_ui::kOsIos,
      FEATURE_WITH_PARAMS_VALUE_TYPE(kTabResumption,
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index cf650d0..bb51eb9 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -510,18 +510,11 @@
 const char kIOSParcelTrackingDescription[] =
     "When enabled, the user will be able to track their packages.";
 
-const char kIOSPasswordAuthOnEntryName[] = "Password Manager Auth on Entry";
-const char kIOSPasswordAuthOnEntryDescription[] =
-    "Requires Local Authentication before showing saved credentials in "
-    "the Password Manager Main Page. Ignored if 'Password Manager Auth on "
-    "Entry V2' is enabled.";
-
 const char kIOSPasswordAuthOnEntryV2Name[] =
     "Password Manager Auth on Entry V2";
 const char kIOSPasswordAuthOnEntryV2Description[] =
     "Requires Local Authentication before showing saved credentials in "
-    "Password Manager subpages. Supersedes `Password Manager Auth on Entry` if "
-    "enabled.";
+    "Password Manager subpages.";
 
 const char kIOSSaveToDriveName[] = "IOS Save to Drive";
 const char kIOSSaveToDriveDescription[] =
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index cc15f960..929aa8e 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -438,11 +438,6 @@
 extern const char kIOSParcelTrackingDescription[];
 
 // Title and description for the flag to require Local Authentication before
-// accessing the Password Manager Main Page.
-extern const char kIOSPasswordAuthOnEntryName[];
-extern const char kIOSPasswordAuthOnEntryDescription[];
-
-// Title and description for the flag to require Local Authentication before
 // accessing the any of the Password Manager surfaces.
 extern const char kIOSPasswordAuthOnEntryV2Name[];
 extern const char kIOSPasswordAuthOnEntryV2Description[];
diff --git a/ios/chrome/browser/push_notification/model/BUILD.gn b/ios/chrome/browser/push_notification/model/BUILD.gn
index d162cfc3..802db26 100644
--- a/ios/chrome/browser/push_notification/model/BUILD.gn
+++ b/ios/chrome/browser/push_notification/model/BUILD.gn
@@ -101,6 +101,7 @@
     "push_notification_account_context_manager+testing.h",
     "push_notification_account_context_manager_unittest.mm",
     "push_notification_client_manager_unittest.mm",
+    "push_notification_settings_util_unittest.mm",
     "push_notification_util+testing.h",
     "push_notification_util_unittest.mm",
   ]
@@ -110,9 +111,16 @@
     ":test_support",
     "//base",
     "//base/test:test_support",
+    "//components/commerce/core:pref_names",
+    "//components/prefs",
+    "//components/prefs:test_support",
+    "//ios/chrome/browser/push_notification/model:constants",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser_state",
     "//ios/chrome/browser/shared/model/browser_state:test_support",
+    "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//ios/chrome/browser/shared/public/features:features",
+    "//ios/chrome/browser/signin/model:fake_system_identity",
     "//ios/chrome/test:test_support",
     "//ios/web/public/test",
     "//testing/gtest",
diff --git a/ios/chrome/browser/push_notification/model/push_notification_settings_util.h b/ios/chrome/browser/push_notification/model/push_notification_settings_util.h
index a0fc640..1a5ceede 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_settings_util.h
+++ b/ios/chrome/browser/push_notification/model/push_notification_settings_util.h
@@ -34,6 +34,11 @@
     const std::string& gaia_id,
     PrefService* pref_service);
 
+// Returns whether the push notification permission status is enabled for any
+// client for mobile notifications.
+BOOL IsMobileNotificationsEnabledForAnyClient(const std::string& gaia_id,
+                                              PrefService* pref_service);
+
 // Returns whether the push notification client's, `client_id`,
 // permission status for mobile notifications is enabled or disabled for the
 // current user.
diff --git a/ios/chrome/browser/push_notification/model/push_notification_settings_util.mm b/ios/chrome/browser/push_notification/model/push_notification_settings_util.mm
index 780ca37..fcc7674 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_settings_util.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_settings_util.mm
@@ -77,6 +77,18 @@
   }
 }
 
+BOOL IsMobileNotificationsEnabledForAnyClient(const std::string& gaia_id,
+                                              PrefService* pref_service) {
+  static std::vector<PushNotificationClientId> client_ids =
+      PushNotificationClientManager::GetClients();
+  for (PushNotificationClientId client_id : client_ids) {
+    if (GetMobileNotificationPermissionStatusForClient(client_id, gaia_id)) {
+      return true;
+    }
+  }
+  return false;
+}
+
 BOOL GetMobileNotificationPermissionStatusForClient(
     PushNotificationClientId client_id,
     const std::string& gaia_id) {
diff --git a/ios/chrome/browser/push_notification/model/push_notification_settings_util_unittest.mm b/ios/chrome/browser/push_notification/model/push_notification_settings_util_unittest.mm
new file mode 100644
index 0000000..16fe00e
--- /dev/null
+++ b/ios/chrome/browser/push_notification/model/push_notification_settings_util_unittest.mm
@@ -0,0 +1,189 @@
+// Copyright 2024 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/push_notification/model/push_notification_settings_util.h"
+
+#import "base/files/file_path.h"
+#import "base/strings/sys_string_conversions.h"
+#import "base/test/scoped_feature_list.h"
+#import "components/commerce/core/pref_names.h"
+#import "components/prefs/pref_service.h"
+#import "components/prefs/scoped_user_pref_update.h"
+#import "ios/chrome/browser/push_notification/model/constants.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_account_context_manager.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_manager.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
+#import "ios/chrome/browser/shared/model/application_context/application_context.h"
+#import "ios/chrome/browser/shared/model/browser_state/browser_state_info_cache.h"
+#import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state.h"
+#import "ios/chrome/browser/shared/model/browser_state/test_chrome_browser_state_manager.h"
+#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/shared/public/features/features.h"
+#import "ios/chrome/browser/signin/model/fake_system_identity.h"
+#import "ios/chrome/test/testing_application_context.h"
+#import "ios/web/public/test/web_task_environment.h"
+#import "testing/gtest/include/gtest/gtest.h"
+#import "testing/gtest_mac.h"
+#import "testing/platform_test.h"
+
+namespace push_notification_settings {
+
+class PushNotificationSettingsUtilTest : public PlatformTest {
+ public:
+  PushNotificationSettingsUtilTest() {
+    test_chrome_browser_state_ = TestChromeBrowserState::Builder().Build();
+    pref_service_ = test_chrome_browser_state_.get()->GetPrefs();
+    default_browser_state_file_path_ =
+        test_chrome_browser_state_->GetStatePath();
+    test_manager_ = std::make_unique<TestChromeBrowserStateManager>(
+        std::move(test_chrome_browser_state_));
+    TestingApplicationContext::GetGlobal()->SetChromeBrowserStateManager(
+        test_manager_.get());
+    browser_state_info()->RemoveBrowserState(default_browser_state_file_path_);
+    manager_ = [[PushNotificationAccountContextManager alloc]
+        initWithChromeBrowserStateManager:test_manager_.get()];
+    fake_id_ = [FakeSystemIdentity fakeIdentity1];
+    // TODO(b/318863934): Remove flag when enabled by default.
+    feature_list_.InitWithFeatures({/*enabled=*/kContentPushNotifications},
+                                   {/*disabled=*/});
+    AddTestCasesToManager(manager_, browser_state_info(),
+                          base::SysNSStringToUTF8(fake_id_.gaiaID),
+                          default_browser_state_file_path_);
+  }
+  BrowserStateInfoCache* browser_state_info() const {
+    return GetApplicationContext()
+        ->GetChromeBrowserStateManager()
+        ->GetBrowserStateInfoCache();
+  }
+  void TurnNotificationForKey(BOOL on,
+                              const std::string key,
+                              PrefService* pref_service) {
+    ScopedDictPrefUpdate update(pref_service,
+                                prefs::kFeaturePushNotificationPermissions);
+    update->Set(key, on);
+  }
+  void TurnEmailNotifications(BOOL on, PrefService* pref_service) {
+    pref_service->SetBoolean(commerce::kPriceEmailNotificationsEnabled, on);
+  }
+  void AddTestCasesToManager(PushNotificationAccountContextManager* manager,
+                             BrowserStateInfoCache* info_cache,
+                             const std::string& gaia_id,
+                             base::FilePath path) {
+    // Construct the BrowserStates with the given gaia id and add the gaia id
+    // into the AccountContextManager.
+    info_cache->AddBrowserState(path, gaia_id, std::u16string());
+    [manager addAccount:gaia_id];
+  }
+
+ protected:
+  PrefService* pref_service_;
+  web::WebTaskEnvironment task_environment_;
+  FakeSystemIdentity* fake_id_;
+  PushNotificationAccountContextManager* manager_;
+  std::unique_ptr<ChromeBrowserState> test_chrome_browser_state_;
+  std::unique_ptr<ios::ChromeBrowserStateManager> test_manager_;
+  base::FilePath default_browser_state_file_path_;
+  base::test::ScopedFeatureList feature_list_;
+};
+
+#pragma mark - All Clients Test
+
+// Tests the overall permission state, which checks the notification state for
+// all clients available on the device. This may include notification types
+// other than push notifications.
+// When 0 clients are enabled, the state is DISABLED.
+// When `enabled` >= 1 AND `disabled` >= 1, the state is INDETERMINANT
+// When `enabled` >=1 and `disabled` == 0, then state is ENABLED.
+TEST_F(PushNotificationSettingsUtilTest, TestPermissionState) {
+  // Enable Notifications in random order.
+  ClientPermissionState state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::DISABLED, state);
+  TurnNotificationForKey(YES, kCommerceNotificationKey, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::INDETERMINANT, state);
+  TurnEmailNotifications(YES, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::INDETERMINANT, state);
+  TurnNotificationForKey(YES, kContentNotificationKey, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::ENABLED, state);
+  // Start disabling in a different order.
+  TurnNotificationForKey(NO, kContentNotificationKey, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::INDETERMINANT, state);
+  TurnNotificationForKey(NO, kCommerceNotificationKey, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::INDETERMINANT, state);
+  TurnEmailNotifications(NO, pref_service_);
+  state = GetNotificationPermissionState(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::DISABLED, state);
+}
+
+#pragma mark - Price Tracking Notification Tests
+
+TEST_F(PushNotificationSettingsUtilTest,
+       TestMobileNotificationsEnabledForCommerce) {
+  BOOL isMobileNotificationsEnabled = IsMobileNotificationsEnabledForAnyClient(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_FALSE(isMobileNotificationsEnabled);
+  TurnNotificationForKey(YES, kCommerceNotificationKey, pref_service_);
+  isMobileNotificationsEnabled = IsMobileNotificationsEnabledForAnyClient(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_TRUE(isMobileNotificationsEnabled);
+}
+
+TEST_F(PushNotificationSettingsUtilTest,
+       TestGetClientPermissionStateForCommerce) {
+  ClientPermissionState state = GetClientPermissionState(
+      PushNotificationClientId::kCommerce,
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::DISABLED, state);
+  TurnNotificationForKey(YES, kCommerceNotificationKey, pref_service_);
+  state = GetClientPermissionState(PushNotificationClientId::kCommerce,
+                                   base::SysNSStringToUTF8(fake_id_.gaiaID),
+                                   pref_service_);
+  EXPECT_EQ(ClientPermissionState::INDETERMINANT, state);
+  TurnEmailNotifications(YES, pref_service_);
+  state = GetClientPermissionState(PushNotificationClientId::kCommerce,
+                                   base::SysNSStringToUTF8(fake_id_.gaiaID),
+                                   pref_service_);
+  EXPECT_EQ(ClientPermissionState::ENABLED, state);
+}
+
+#pragma mark - Content Notification Tests
+
+TEST_F(PushNotificationSettingsUtilTest,
+       TestMobileNotificationsEnabledForContent) {
+  BOOL isMobileNotificationsEnabled = IsMobileNotificationsEnabledForAnyClient(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_FALSE(isMobileNotificationsEnabled);
+  TurnNotificationForKey(YES, kContentNotificationKey, pref_service_);
+  isMobileNotificationsEnabled = IsMobileNotificationsEnabledForAnyClient(
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_TRUE(isMobileNotificationsEnabled);
+}
+
+TEST_F(PushNotificationSettingsUtilTest,
+       TestGetClientPermissionStateForContent) {
+  ClientPermissionState state = GetClientPermissionState(
+      PushNotificationClientId::kContent,
+      base::SysNSStringToUTF8(fake_id_.gaiaID), pref_service_);
+  EXPECT_EQ(ClientPermissionState::DISABLED, state);
+  TurnNotificationForKey(YES, kContentNotificationKey, pref_service_);
+  state = GetClientPermissionState(PushNotificationClientId::kContent,
+                                   base::SysNSStringToUTF8(fake_id_.gaiaID),
+                                   pref_service_);
+  EXPECT_EQ(ClientPermissionState::ENABLED, state);
+}
+
+}  // namespace push_notification_settings
diff --git a/ios/chrome/browser/safe_browsing/model/safe_browsing_egtest.mm b/ios/chrome/browser/safe_browsing/model/safe_browsing_egtest.mm
index fe255f8b..a20e59a 100644
--- a/ios/chrome/browser/safe_browsing/model/safe_browsing_egtest.mm
+++ b/ios/chrome/browser/safe_browsing/model/safe_browsing_egtest.mm
@@ -613,7 +613,15 @@
 
 // Tests that performing session restoration to a Safe Browsing warning page
 // preserves navigation history.
-- (void)testRestoreToWarningPagePreservesHistory {
+// TODO(crbug.com/1516583):  Test is flaky on device. Re-enable the test.
+#if !TARGET_OS_SIMULATOR
+#define MAYBE_testRestoreToWarningPagePreservesHistory \
+  FLAKY_testRestoreToWarningPagePreservesHistory
+#else
+#define MAYBE_testRestoreToWarningPagePreservesHistory \
+  testRestoreToWarningPagePreservesHistory
+#endif
+- (void)MAYBE_testRestoreToWarningPagePreservesHistory {
   // Build up navigation history that consists of a safe URL, a warning page,
   // and another safe URL.
   [ChromeEarlGrey loadURL:_safeURL1];
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/BUILD.gn b/ios/chrome/browser/ui/autofill/form_input_accessory/BUILD.gn
index aa42291..2287f5e 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/BUILD.gn
@@ -57,7 +57,6 @@
     "//ios/chrome/browser/ui/autofill/manual_fill",
     "//ios/chrome/browser/ui/autofill/manual_fill:manual_fill_ui",
     "//ios/chrome/browser/ui/bubble",
-    "//ios/chrome/browser/ui/settings/password:features",
     "//ios/chrome/browser/ui/toolbar/public",
     "//ios/chrome/common:button_config",
     "//ios/chrome/common/ui/colors:colors",
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
index eac4ba6..d6e1113a 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.mm
@@ -57,7 +57,6 @@
 #import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h"
 #import "ios/chrome/browser/ui/bubble/bubble_constants.h"
 #import "ios/chrome/browser/ui/bubble/bubble_view_controller_presenter.h"
-#import "ios/chrome/browser/ui/settings/password/password_manager_ui_features.h"
 #import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
 #import "ios/chrome/grit/ios_branded_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
@@ -396,10 +395,7 @@
   // authentication when entering the Password Manager. Resigning the first
   // responder here fixes the issue without removing the focus on the underlying
   // web view's field. See crbug.com/1494929.
-  if (password_manager::features::IsAuthOnEntryEnabled() ||
-      password_manager::features::IsAuthOnEntryV2Enabled()) {
     [GetFirstResponder() resignFirstResponder];
-  }
 
   UMA_HISTOGRAM_ENUMERATION(
       "PasswordManager.ManagePasswordsReferrer",
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
index 46cbc0a..577c54f 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
@@ -4,10 +4,13 @@
 
 #import "ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.h"
 
+#import <UserNotifications/UserNotifications.h>
+
 #import "base/feature_list.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
+#import "base/strings/sys_string_conversions.h"
 #import "base/time/time.h"
 #import "components/prefs/pref_service.h"
 #import "components/signin/public/identity_manager/identity_manager.h"
@@ -16,6 +19,7 @@
 #import "ios/chrome/browser/push_notification/model/notifications_alert_presenter.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_service.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_settings_util.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_util.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
@@ -263,6 +267,32 @@
   return self.identityManager->HasPrimaryAccount(consent);
 }
 
+// Returns true if notifications are enabled in Chime or at the OS level.
+- (BOOL)isNotificationsEnabled {
+  DCHECK([self isUserSignedIn]);
+  id<SystemIdentity> identity = self.authenticationService->GetPrimaryIdentity(
+      signin::ConsentLevel::kSignin);
+  // Check if user has notifications enabled at the Chime level.
+  BOOL isChimeEnabled =
+      push_notification_settings::IsMobileNotificationsEnabledForAnyClient(
+          base::SysNSStringToUTF8(identity.gaiaID), self.prefService);
+  if (isChimeEnabled) {
+    return true;
+  }
+  // Check the user's OS notification permission status for Chrome.
+  __block UNAuthorizationStatus status;
+  [PushNotificationUtil
+      getPermissionSettings:^(UNNotificationSettings* settings) {
+        status = settings.authorizationStatus;
+      }];
+
+  if (status != UNAuthorizationStatusNotDetermined &&
+      status != UNAuthorizationStatusDenied) {
+    return true;
+  }
+  return false;
+}
+
 // TODO(b/315161586): Disable notifications promo if DSE changes.
 - (BOOL)shouldShowNotificationsPromo {
   // Check feature flag.
@@ -280,6 +310,11 @@
     return true;
   }
 
+  // Check if notifications are enabled of any type at the Chime level.
+  if ([self isNotificationsEnabled]) {
+    return false;
+  }
+
   int notificationsPromoTimesShown =
       self.prefService->GetInteger(prefs::kNotificationsPromoTimesShown);
   int notificationsPromoTimesDismissed =
diff --git a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
index 1c85800..845909b 100644
--- a/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
+++ b/ios/chrome/browser/ui/ntp/new_tab_page_view_controller.mm
@@ -462,10 +462,6 @@
     _feedContainer.translatesAutoresizingMaskIntoConstraints = NO;
     _feedContainer.backgroundColor = [UIColor colorNamed:kBackgroundColor];
 
-    // Reduce the zPosition so that the container appears behind the feed
-    // content.
-    _feedContainer.layer.zPosition = -1;
-
     // Add corner radius to the top border.
     _feedContainer.clipsToBounds = YES;
     _feedContainer.layer.cornerRadius = kHomeModuleContainerCornerRadius;
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 1c88de3..0f208aa 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
@@ -383,7 +383,7 @@
     [self.passwordSharingFirstRunCoordinator stop];
     self.passwordSharingFirstRunCoordinator =
         [[PasswordSharingFirstRunCoordinator alloc]
-            initWithBaseViewController:self.baseViewController
+            initWithBaseViewController:self.viewController
                                browser:self.browser];
     self.passwordSharingFirstRunCoordinator.delegate = self;
     [self.passwordSharingFirstRunCoordinator start];
@@ -459,7 +459,7 @@
   // password details.
   [self dismissAlertCoordinator];
   [self dismissActionSheetCoordinator];
-  [self dismissPasswordSharingCoordinator];
+  [self stopPasswordSharingCoordinator];
   [self stopPasswordSharingFirstRunCoordinatorWithCompletion:nil];
 }
 
@@ -505,11 +505,6 @@
   self.alertCoordinator = nil;
 }
 
-- (void)dismissPasswordSharingCoordinator {
-  [_passwordSharingCoordinator stop];
-  _passwordSharingCoordinator = nil;
-}
-
 // Starts reauthCoordinator. If Password Details was opened from outside the
 // Password Manager, Local Authentication is required. Once started
 // reauthCoordinator observes scene state changes and requires authentication
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
index 14bb2447..b4ab5f66 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_egtest.mm
@@ -714,16 +714,6 @@
             (testOpenPasswordSettingsSubmenuWithFailedAuth)] ||
       [self isRunningTest:@selector(testAddNewPasswordWithFailedAuth)]) {
     config.features_enabled.push_back(
-        password_manager::features::kIOSPasswordAuthOnEntry);
-    config.features_enabled.push_back(
-        password_manager::features::kIOSPasswordAuthOnEntryV2);
-  }
-
-  if ([self isRunningTest:@selector
-            (testPasswordManagerVisitMetricWithoutAuthRequired)]) {
-    config.features_disabled.push_back(
-        password_manager::features::kIOSPasswordAuthOnEntry);
-    config.features_disabled.push_back(
         password_manager::features::kIOSPasswordAuthOnEntryV2);
   }
 
@@ -755,8 +745,6 @@
         password_manager::features::
             kIOSPasswordSettingsBulkUploadLocalPasswords);
     config.features_disabled.push_back(
-        password_manager::features::kIOSPasswordAuthOnEntry);
-    config.features_disabled.push_back(
         password_manager::features::kIOSPasswordAuthOnEntryV2);
   }
 
@@ -3368,9 +3356,6 @@
 - (void)testSavePasswordsInAccountFlowAuthFailed {
   SavePasswordFormToProfileStore(@"password1", @"user1",
                                  @"https://example1.com");
-
-  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
-                                    ReauthenticationResult::kFailure];
   FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
   [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity enableSync:NO];
 
@@ -3386,6 +3371,9 @@
       performAction:grey_tap()];
   [ChromeEarlGreyUI waitForAppToIdle];
 
+  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
+                                    ReauthenticationResult::kFailure];
+
   // Tap on "Save in Account" (accept) button.
   [SaveInAccountConfirmationDialogButton() performAction:grey_tap()];
   [ChromeEarlGreyUI waitForAppToIdle];
@@ -3399,8 +3387,6 @@
 - (void)testSavePasswordsInAccountFlowNoAuthSetOnDevice {
   SavePasswordFormToProfileStore(@"password1", @"user1",
                                  @"https://example1.com");
-
-  [PasswordSettingsAppInterface mockReauthenticationModuleCanAttempt:NO];
   FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
   [SigninEarlGreyUI signinWithFakeIdentity:fakeIdentity enableSync:NO];
 
@@ -3416,6 +3402,8 @@
       performAction:grey_tap()];
   [ChromeEarlGreyUI waitForAppToIdle];
 
+  [PasswordSettingsAppInterface mockReauthenticationModuleCanAttempt:NO];
+
   // Tap on "Save in Account" (accept) button.
   [SaveInAccountConfirmationDialogButton() performAction:grey_tap()];
   [ChromeEarlGreyUI waitForAppToIdle];
@@ -3575,16 +3563,6 @@
   CheckPasswordManagerVisitMetricCount(0);
 }
 
-// Tests that password manager visit histogram is recorded after opening
-// password manager without authentication required.
-- (void)testPasswordManagerVisitMetricWithoutAuthRequired {
-  OpenPasswordManager();
-
-  CheckPasswordManagerVisitMetricCount(1);
-
-  CheckReauthenticationUIEventMetricTotalCount(0);
-}
-
 // Tests that the Password Manager is opened is search mode when opened from the
 // Search Passwords widget.
 - (void)testOpenSearchPasswordsWidget {
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_ui_features.h b/ios/chrome/browser/ui/settings/password/password_manager_ui_features.h
index c936970..8bc2064 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_ui_features.h
+++ b/ios/chrome/browser/ui/settings/password/password_manager_ui_features.h
@@ -11,11 +11,6 @@
 // components.
 namespace password_manager::features {
 
-BASE_DECLARE_FEATURE(kIOSPasswordAuthOnEntry);
-
-// Helper function returning the status of `kIOSPasswordAuthOnEntry`.
-bool IsAuthOnEntryEnabled();
-
 BASE_DECLARE_FEATURE(kIOSPasswordAuthOnEntryV2);
 
 // Helper function returning the status of `kIOSPasswordAuthOnEntry2`.
diff --git a/ios/chrome/browser/ui/settings/password/password_manager_ui_features.mm b/ios/chrome/browser/ui/settings/password/password_manager_ui_features.mm
index e2453ec..ce4db27 100644
--- a/ios/chrome/browser/ui/settings/password/password_manager_ui_features.mm
+++ b/ios/chrome/browser/ui/settings/password/password_manager_ui_features.mm
@@ -6,16 +6,6 @@
 
 namespace password_manager::features {
 // When enabled, local authentication (Face ID, Touch ID or Passcode) is
-// required to view saved credentials in the Password Manager Main Page.
-BASE_FEATURE(kIOSPasswordAuthOnEntry,
-             "IOSPasswordAuthOnEntry",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
-bool IsAuthOnEntryEnabled() {
-  return base::FeatureList::IsEnabled(kIOSPasswordAuthOnEntry);
-}
-
-// When enabled, local authentication (Face ID, Touch ID or Passcode) is
 // required to view saved credentials in all Password Manager Surfaces.
 BASE_FEATURE(kIOSPasswordAuthOnEntryV2,
              "IOSPasswordAuthOnEntryV2",
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
index 67ae84b0..f4f00bbf 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
@@ -47,6 +47,16 @@
       l10n_util::GetNSString(IDS_IOS_PASSWORD_SHARING_FIRST_RUN_TITLE));
 }
 
+// Matcher for the UITableView inside the Family Picker View.
+id<GREYMatcher> FamilyPickerTableViewMatcher() {
+  return grey_accessibilityID(kFamilyPickerTableViewID);
+}
+
+// Matcher for the Password Picker View.
+id<GREYMatcher> PasswordPickerViewMatcher() {
+  return grey_accessibilityID(kPasswordPickerViewID);
+}
+
 }  // namespace
 
 // Test case for the Password Sharing flow.
@@ -124,7 +134,11 @@
   }
 
   if ([self isRunningTest:@selector
-            (testFirstRunExperienceViewDismissedForAuthentication)]) {
+            (testFirstRunExperienceViewDismissedForAuthentication)] ||
+      [self isRunningTest:@selector
+            (testPasswordPickerViewDismissedForAuthentication)] ||
+      [self isRunningTest:@selector
+            (testFamilyPickerViewDismissedForAuthentication)]) {
     config.features_enabled.push_back(
         password_manager::features::kIOSPasswordAuthOnEntryV2);
   }
@@ -287,8 +301,7 @@
       selectElementWithMatcher:grey_accessibilityID(kPasswordShareButtonID)]
       performAction:grey_tap()];
 
-  [[EarlGrey
-      selectElementWithMatcher:grey_accessibilityID(kPasswordPickerViewID)]
+  [[EarlGrey selectElementWithMatcher:PasswordPickerViewMatcher()]
       performAction:grey_swipeFastInDirection(kGREYDirectionDown)];
 
   // Check that the current view is the password details view.
@@ -528,8 +541,7 @@
       performAction:grey_tap()];
 
   // Scroll down to the last recipient (the ineligible ones are on the bottom).
-  [[EarlGrey
-      selectElementWithMatcher:grey_accessibilityID(kFamilyPickerTableViewID)]
+  [[EarlGrey selectElementWithMatcher:FamilyPickerTableViewMatcher()]
       performAction:grey_scrollToContentEdge(kGREYContentEdgeBottom)];
   // Tap on the info button next to the ineligible recipient row.
   [[EarlGrey
@@ -599,8 +611,7 @@
       performAction:grey_tap()];
 
   // Check that the current view is the family picker view.
-  [[EarlGrey
-      selectElementWithMatcher:grey_accessibilityID(kFamilyPickerTableViewID)]
+  [[EarlGrey selectElementWithMatcher:FamilyPickerTableViewMatcher()]
       assertWithMatcher:grey_notNil()];
 
   // Tap the cancel button.
@@ -613,8 +624,7 @@
   [[EarlGrey
       selectElementWithMatcher:grey_accessibilityID(kPasswordShareButtonID)]
       performAction:grey_tap()];
-  [[EarlGrey
-      selectElementWithMatcher:grey_accessibilityID(kFamilyPickerTableViewID)]
+  [[EarlGrey selectElementWithMatcher:FamilyPickerTableViewMatcher()]
       assertWithMatcher:grey_notNil()];
 }
 
@@ -664,9 +674,59 @@
   // Background then foreground app so reauthentication UI is displayed.
   [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
 
-  // Check that first run experience is gone.
+  // Check that first run experience is gone and password details is visible.
   [[EarlGrey selectElementWithMatcher:PasswordSharingFirstRunMatcher()]
       assertWithMatcher:grey_nil()];
+
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kPasswordDetailsViewControllerID)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+}
+
+- (void)testFamilyPickerViewDismissedForAuthentication {
+  SignInAndEnableSync();
+  [self saveExamplePasswordToProfileStoreAndOpenDetails];
+
+  [[EarlGrey
+      selectElementWithMatcher:grey_accessibilityID(kPasswordShareButtonID)]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:FamilyPickerTableViewMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Background then foreground app so reauthentication UI is displayed.
+  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
+
+  // Check that the family picker is gone and password details is visible.
+  [[EarlGrey selectElementWithMatcher:FamilyPickerTableViewMatcher()]
+      assertWithMatcher:grey_nil()];
+
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kPasswordDetailsViewControllerID)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+}
+
+- (void)testPasswordPickerViewDismissedForAuthentication {
+  SignInAndEnableSync();
+  [self saveExamplePasswordsToProfileStoreAndOpenDetails];
+
+  [[EarlGrey
+      selectElementWithMatcher:grey_accessibilityID(kPasswordShareButtonID)]
+      performAction:grey_tap()];
+
+  [[EarlGrey selectElementWithMatcher:PasswordPickerViewMatcher()]
+      assertWithMatcher:grey_sufficientlyVisible()];
+
+  // Background then foreground app so reauthentication UI is displayed.
+  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];
+
+  // Check that the password picker is gone and password details is visible.
+  [[EarlGrey selectElementWithMatcher:PasswordPickerViewMatcher()]
+      assertWithMatcher:grey_nil()];
+
+  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
+                                          kPasswordDetailsViewControllerID)]
+      assertWithMatcher:grey_sufficientlyVisible()];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator.mm
index c884979..3eca2e2 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator.mm
@@ -135,14 +135,6 @@
   [self.delegate startPasswordSharing];
 }
 
-- (void)learnMoreLinkWasTapped {
-  LogPasswordSharingInteraction(
-      PasswordSharingInteraction::kSharingConfirmationLearnMoreClicked);
-
-  [self openURLInNewTabAndCloseSettings:GURL(kPasswordSharingLearnMoreURL)];
-  [self.delegate sharingStatusCoordinatorWasDismissed:self];
-}
-
 - (void)changePasswordLinkWasTapped {
   CHECK(_changePasswordURL.has_value());
 
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator_unittest.mm b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator_unittest.mm
index 9a81ac6..60c49a7f 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_coordinator_unittest.mm
@@ -56,24 +56,6 @@
   id mock_application_settings_commands_handler_;
 };
 
-TEST_F(SharingStatusCoordinatorTest, OpensHelpCenterOnLearnMoreTap) {
-  base::HistogramTester histogram_tester;
-
-  OCMExpect([mock_application_commands_handler_
-      closeSettingsUIAndOpenURL:[OCMArg checkWithBlock:^BOOL(
-                                            OpenNewTabCommand* command) {
-        return command.URL ==
-               GURL("https://support.google.com/chrome/?p=password_sharing");
-      }]]);
-  [(id<SharingStatusViewControllerPresentationDelegate>)
-          coordinator_ learnMoreLinkWasTapped];
-  EXPECT_OCMOCK_VERIFY(mock_application_commands_handler_);
-
-  histogram_tester.ExpectUniqueSample(
-      "PasswordManager.PasswordSharingIOS.UserAction",
-      PasswordSharingInteraction::kSharingConfirmationLearnMoreClicked, 1);
-}
-
 TEST_F(SharingStatusCoordinatorTest, RedirectsToSiteOnChangePasswordURLTap) {
   base::HistogramTester histogram_tester;
 
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller.mm
index cf05651..6303fc8 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller.mm
@@ -67,7 +67,6 @@
 
 // Accessibility identifiers of text views with links.
 NSString* const kSharingStatusFooterId = @"SharingStatusViewFooter";
-NSString* const kSharingStatusSubtitleId = @"SharingStatusViewSubtitle";
 
 }  // namespace
 
@@ -249,11 +248,7 @@
     shouldInteractWithURL:(NSURL*)URL
                   inRange:(NSRange)characterRange
               interaction:(UITextItemInteraction)interaction {
-  if (textView.accessibilityIdentifier == kSharingStatusSubtitleId) {
-    [self.delegate learnMoreLinkWasTapped];
-  } else if (textView.accessibilityIdentifier == kSharingStatusFooterId) {
-    [self.delegate changePasswordLinkWasTapped];
-  }
+  [self.delegate changePasswordLinkWasTapped];
   return NO;
 }
 
@@ -652,17 +647,14 @@
   UITextView* subtitle = [self createTextView];
   subtitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
   subtitle.textColor = [UIColor colorNamed:kTextPrimaryColor];
-  subtitle.accessibilityIdentifier = kSharingStatusSubtitleId;
 
   StringWithTags stringWithBolds =
       ParseStringWithTags(self.subtitleString, kBeginBoldTag, kEndBoldTag);
-  StringWithTags stringWithLinks = ParseStringWithLinks(stringWithBolds.string);
-  subtitle.text = stringWithLinks.string;
+  subtitle.text = stringWithBolds.string;
 
   for (const NSRange& range : stringWithBolds.ranges) {
     [self addBoldAttributeToTextView:subtitle range:range];
   }
-  [self addLinkAttributeToTextView:subtitle range:stringWithLinks.ranges[0]];
 
   return subtitle;
 }
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller_presentation_delegate.h b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller_presentation_delegate.h
index 7d243728..f4880d2 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller_presentation_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/sharing_status_view_controller_presentation_delegate.h
@@ -18,9 +18,6 @@
 // sharing coordinator.
 - (void)startPasswordSharing;
 
-// Handles taps on the link to learn more about the password sharing feature.
-- (void)learnMoreLinkWasTapped;
-
 // Handles taps on the link to the site where the user can change the password
 // that was shared.
 - (void)changePasswordLinkWasTapped;
diff --git a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
index 433c2cc..b7014af 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_coordinator.mm
@@ -193,23 +193,16 @@
 
   self.mediator.consumer = self.passwordsViewController;
 
-  BOOL startBlockedForReauth =
-      password_manager::features::IsAuthOnEntryEnabled() ||
-      password_manager::features::IsAuthOnEntryV2Enabled();
   // Disable animation when content will be blocked for reauth to prevent
   // flickering in navigation bar.
   [self.baseNavigationController pushViewController:self.passwordsViewController
-                                           animated:!startBlockedForReauth];
+                                           animated:NO];
 
   _visitsRecorder = [[IOSPasswordManagerVisitsRecorder alloc]
       initWithPasswordManagerSurface:password_manager::PasswordManagerSurface::
                                          kPasswordList];
 
-  if (startBlockedForReauth) {
-    [self startReauthCoordinatorWithAuthOnStart:YES];
-  } else {
-    [_visitsRecorder maybeRecordVisitMetric];
-  }
+  [self startReauthCoordinatorWithAuthOnStart:YES];
 
   // Start a password check.
   [self checkSavedPasswords];
diff --git a/ios/chrome/browser/ui/settings/password/reauthentication/reauthentication_coordinator.mm b/ios/chrome/browser/ui/settings/password/reauthentication/reauthentication_coordinator.mm
index 143ba7a..42d4d224 100644
--- a/ios/chrome/browser/ui/settings/password/reauthentication/reauthentication_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/reauthentication/reauthentication_coordinator.mm
@@ -268,8 +268,9 @@
   UIViewController* presentedViewController =
       topViewController.presentedViewController;
   // Do not dismiss the Search Controller, otherwise pushViewController does not
-  // add a the new view controller to the top of the navigation stack.
-  if (![presentedViewController isKindOfClass:[UISearchController class]]) {
+  // add the new view controller to the top of the navigation stack.
+  if (![presentedViewController isKindOfClass:[UISearchController class]] &&
+      !presentedViewController.isBeingDismissed) {
     [presentedViewController.presentingViewController
         dismissViewControllerAnimated:NO
                            completion:nil];
diff --git a/ios_internal b/ios_internal
index 61b3244..ad7dea6 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 61b32446e8a800b02f9bbaf7a2dc49b9d752b034
+Subproject commit ad7dea67e79c6d152370879b33caf38c79c7e160
diff --git a/media/base/android/media_drm_bridge_unittest.cc b/media/base/android/media_drm_bridge_unittest.cc
index 5a4f4e2..0cdc6f8 100644
--- a/media/base/android/media_drm_bridge_unittest.cc
+++ b/media/base/android/media_drm_bridge_unittest.cc
@@ -331,6 +331,8 @@
   scoped_feature_list_.InitWithFeatures({media::kExternalClearKeyForTesting},
                                         {});
 
+  // TODO(b/263310318): Remove test skip when clear key is fixed and we call
+  // into MediaDrm for Android ClearKey instead of using AesDecryptor.
   if (!MediaDrmBridge::IsKeySystemSupported(kExternalClearKeyKeySystem)) {
     GTEST_SKIP() << "ClearKey not supported on device.";
   }
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index d482ac9..ad30f58 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -4,7 +4,6 @@
 
 import("//media/gpu/args.gni")
 import("//media/media_options.gni")
-import("//third_party/libgav1/options.gni")
 
 source_set("filters") {
   # Do not expand the visibility here without double-checking with OWNERS, this
@@ -53,8 +52,6 @@
     "offloading_video_decoder.h",
     "pipeline_controller.cc",
     "pipeline_controller.h",
-    "resolution_monitor.cc",
-    "resolution_monitor.h",
     "source_buffer_parse_warnings.h",
     "source_buffer_range.cc",
     "source_buffer_range.h",
@@ -84,8 +81,6 @@
 
   configs += [ "//media:subcomponent_config" ]
 
-  assert(use_libgav1_parser)
-
   deps = [
     "//base",
     "//build:chromeos_buildflags",
@@ -94,9 +89,7 @@
     "//media/base",
     "//media/cdm",
     "//media/formats",
-    "//media/parsers",
     "//media/video",
-    "//third_party/libgav1:libgav1_parser",
     "//third_party/libyuv",
     "//ui/gfx/geometry:geometry",
   ]
@@ -327,7 +320,6 @@
     "memory_data_source_unittest.cc",
     "offloading_video_decoder_unittest.cc",
     "pipeline_controller_unittest.cc",
-    "resolution_monitor_unittest.cc",
     "source_buffer_state_unittest.cc",
     "source_buffer_stream_unittest.cc",
     "stream_parser_factory_unittest.cc",
diff --git a/media/filters/resolution_monitor.h b/media/filters/resolution_monitor.h
deleted file mode 100644
index 2175fc6..0000000
--- a/media/filters/resolution_monitor.h
+++ /dev/null
@@ -1,35 +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 MEDIA_FILTERS_RESOLUTION_MONITOR_H_
-#define MEDIA_FILTERS_RESOLUTION_MONITOR_H_
-
-#include <memory>
-
-#include "base/sequence_checker.h"
-#include "media/base/media_export.h"
-#include "media/base/video_codecs.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-#include "ui/gfx/geometry/size.h"
-
-namespace media {
-
-class DecoderBuffer;
-
-// Resolution monitor acquires a resolution of DecoderBuffer by parsing it. We
-// can know the stream resolution before the first keyframe is decoded. This
-// avoids requesting a sender to produce a keyframe again when a software
-// decoder fallback due to a stream resolution happens.
-class MEDIA_EXPORT ResolutionMonitor {
- public:
-  virtual ~ResolutionMonitor();
-
-  static std::unique_ptr<ResolutionMonitor> Create(VideoCodec codec);
-
-  virtual absl::optional<gfx::Size> GetResolution(
-      const DecoderBuffer& buffer) = 0;
-  virtual VideoCodec codec() const = 0;
-};
-}  // namespace media
-#endif  // MEDIA_FILTERS_RESOLUTION_MONITOR_H_
diff --git a/net/cookies/cookie_inclusion_status.cc b/net/cookies/cookie_inclusion_status.cc
index 5be49154..d2c184d 100644
--- a/net/cookies/cookie_inclusion_status.cc
+++ b/net/cookies/cookie_inclusion_status.cc
@@ -76,6 +76,9 @@
   // If the cookie would be excluded for reasons other than the new SameSite
   // rules, don't bother warning about it.
   MaybeClearSameSiteWarning();
+  // If the cookie would be excluded for reasons unrelated to 3pcd, don't bother
+  // warning about 3pcd.
+  MaybeClearThirdPartyPhaseoutReason();
 }
 
 void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) {
@@ -118,6 +121,19 @@
   }
 }
 
+void CookieInclusionStatus::MaybeClearThirdPartyPhaseoutReason() {
+  if (!IsInclude()) {
+    RemoveWarningReason(WARN_THIRD_PARTY_PHASEOUT);
+  }
+  if (ExclusionReasonsWithout(
+          {EXCLUDE_THIRD_PARTY_PHASEOUT,
+           EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}) != 0u) {
+    // TODO(crbug.com/1516673): Once the bug is fixed, also remove
+    // EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET.
+    RemoveExclusionReason(EXCLUDE_THIRD_PARTY_PHASEOUT);
+  }
+}
+
 bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const {
   return ExclusionReasonsWithout({
              EXCLUDE_SAMESITE_STRICT,
diff --git a/net/cookies/cookie_inclusion_status.h b/net/cookies/cookie_inclusion_status.h
index fa6562e..98d8012 100644
--- a/net/cookies/cookie_inclusion_status.h
+++ b/net/cookies/cookie_inclusion_status.h
@@ -392,6 +392,11 @@
   ExclusionReasonBitset ExclusionReasonsWithout(
       const std::vector<ExclusionReason>& reasons) const;
 
+  // If the cookie would have been excluded by reasons that are not
+  // Third-party cookie phaseout related, clear the Third-party cookie phaseout
+  // warning/exclusion reason in this case.
+  void MaybeClearThirdPartyPhaseoutReason();
+
   // A bit vector of the applicable exclusion reasons.
   ExclusionReasonBitset exclusion_reasons_;
 
diff --git a/net/cookies/cookie_inclusion_status_unittest.cc b/net/cookies/cookie_inclusion_status_unittest.cc
index 1281720..9378b456 100644
--- a/net/cookies/cookie_inclusion_status_unittest.cc
+++ b/net/cookies/cookie_inclusion_status_unittest.cc
@@ -50,14 +50,80 @@
       CookieInclusionStatus status_two_reasons = status_one_reason;
       status_two_reasons.AddExclusionReason(reason2);
       EXPECT_FALSE(status_two_reasons.IsInclude());
-      EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason1));
-      EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason2));
-      EXPECT_FALSE(status_two_reasons.HasOnlyExclusionReason(reason1));
-      EXPECT_FALSE(status_two_reasons.HasOnlyExclusionReason(reason2));
+
+      if (reason1 != CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT &&
+          reason2 != CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT) {
+        EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason1));
+        EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason2));
+      }
     }
   }
 }
 
+TEST(CookieInclusionStatusTest,
+     ExcludeStatus_MaybeClearThirdPartyPhaseoutReason) {
+  int num_exclusion_reasons =
+      static_cast<int>(CookieInclusionStatus::NUM_EXCLUSION_REASONS);
+  CookieInclusionStatus::ExclusionReason reason1 =
+      CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT;
+  const CookieInclusionStatus status_one_reason(reason1);
+  ASSERT_FALSE(status_one_reason.IsInclude());
+  ASSERT_TRUE(status_one_reason.HasOnlyExclusionReason(reason1));
+
+  for (int j = 0; j < num_exclusion_reasons; ++j) {
+    auto reason2 = static_cast<CookieInclusionStatus::ExclusionReason>(j);
+    if (reason1 == reason2) {
+      continue;
+    }
+    EXPECT_FALSE(status_one_reason.HasExclusionReason(reason2)) << reason2;
+
+    CookieInclusionStatus status_two_reasons = status_one_reason;
+    status_two_reasons.AddExclusionReason(reason2);
+    EXPECT_FALSE(status_two_reasons.IsInclude());
+
+    if (reason2 == CookieInclusionStatus::
+                       EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET) {
+      EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason1));
+      EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason2));
+    } else {
+      EXPECT_TRUE(status_two_reasons.HasOnlyExclusionReason(reason2));
+    }
+  }
+}
+
+TEST(CookieInclusionStatusTest,
+     AddExclusionReason_MaybeClearThirdPartyPhaseoutReason) {
+  CookieInclusionStatus status;
+  status.AddWarningReason(CookieInclusionStatus::WARN_THIRD_PARTY_PHASEOUT);
+  ASSERT_TRUE(status.ShouldWarn());
+  ASSERT_TRUE(status.HasExactlyWarningReasonsForTesting(
+      {CookieInclusionStatus::WARN_THIRD_PARTY_PHASEOUT}));
+  // Adding an exclusion reason should clear 3PCD warning reason.
+  status.AddExclusionReason(
+      CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT);
+  EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+      {CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT}));
+  EXPECT_FALSE(status.ShouldWarn());
+
+  status.AddExclusionReason(
+      CookieInclusionStatus::
+          EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET);
+  EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+      {CookieInclusionStatus::EXCLUDE_THIRD_PARTY_PHASEOUT,
+       CookieInclusionStatus::
+           EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}));
+  // Adding an exclusion reason unrelated with 3PCD should clear 3PCD related
+  // exclusion reasons.
+  status.AddExclusionReason(
+      CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
+  EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting(
+      {CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE,
+       // TODO(crbug.com/1516673): This should also be removed.
+       CookieInclusionStatus::
+           EXCLUDE_THIRD_PARTY_BLOCKED_WITHIN_FIRST_PARTY_SET}));
+  EXPECT_FALSE(status.IsInclude());
+}
+
 TEST(CookieInclusionStatusTest, AddExclusionReason) {
   CookieInclusionStatus status;
   status.AddWarningReason(
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 3b53f58..894328ab 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -3607,15 +3607,14 @@
   if (migrate_idle_sessions) {
     quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
     // A RESET will be sent to the peer to cancel the non-migratable stream.
-    quic_data1.AddWrite(SYNCHRONOUS,  // 3
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
+    quic_data1.AddWrite(SYNCHRONOUS,
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_num++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
     quic_data1.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakeRetransmissionAndRstPacket(
-            1, packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED));
+        SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(1, packet_num++));
     // Ping packet to send after migration is completed.
     quic_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++));
@@ -3624,9 +3623,11 @@
   } else {
     client_maker_.set_connection_id(cid_on_old_path);
     socket_data.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeDataAckAndConnectionClosePacket(
+        SYNCHRONOUS, client_maker_.MakeDataRstAckAndConnectionClosePacket(
                          packet_num++, GetQpackDecoderStreamId(),
-                         StreamCancellationQpackDecoderInstruction(0), 1, 1,
+                         StreamCancellationQpackDecoderInstruction(0),
+                         GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED, 1, 1,
                          quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
                          "net error", /*path_response_frame*/ 0x1b));
   }
@@ -7530,14 +7531,13 @@
     quic_data1.AddRead(SYNCHRONOUS, ERR_IO_PENDING);  // Hanging read.
     // A RESET will be sent to the peer to cancel the non-migratable stream.
     quic_data1.AddWrite(SYNCHRONOUS,
-                        client_maker_.MakeDataPacket(
-                            packet_num++, GetQpackDecoderStreamId(), false,
-                            StreamCancellationQpackDecoderInstruction(0)));
+                        client_maker_.MakeDataAndRstPacket(
+                            packet_num++, GetQpackDecoderStreamId(),
+                            StreamCancellationQpackDecoderInstruction(0),
+                            GetNthClientInitiatedBidirectionalStreamId(0),
+                            quic::QUIC_STREAM_CANCELLED));
     quic_data1.AddWrite(
-        SYNCHRONOUS,
-        client_maker_.MakeRetransmissionAndRstPacket(
-            1, packet_num++, GetNthClientInitiatedBidirectionalStreamId(0),
-            quic::QUIC_STREAM_CANCELLED));
+        SYNCHRONOUS, client_maker_.MakeRetransmissionPacket(1, packet_num++));
     // Ping packet to send after migration is completed.
     quic_data1.AddWrite(SYNCHRONOUS,
                         client_maker_.MakePingPacket(packet_num++));
@@ -7546,9 +7546,11 @@
   } else {
     client_maker_.set_connection_id(cid_on_old_path);
     socket_data.AddWrite(
-        SYNCHRONOUS, client_maker_.MakeDataAckAndConnectionClosePacket(
+        SYNCHRONOUS, client_maker_.MakeDataRstAckAndConnectionClosePacket(
                          packet_num++, GetQpackDecoderStreamId(),
-                         StreamCancellationQpackDecoderInstruction(0), 1, 1,
+                         StreamCancellationQpackDecoderInstruction(0),
+                         GetNthClientInitiatedBidirectionalStreamId(0),
+                         quic::QUIC_STREAM_CANCELLED, 1, 1,
                          quic::QUIC_CONNECTION_MIGRATION_NO_MIGRATABLE_STREAMS,
                          "net error", /*path_response_frame*/ 0x1b));
   }
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index cd26252..2babf82 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -527,8 +527,8 @@
   InitializeHeader(num);
 
   AddQuicAckFrame(largest_received, smallest_received);
-  AddQuicStreamFrame(data_id, fin, data);
   AddQuicRstStreamFrame(stream_id, error_code);
+  AddQuicStreamFrame(data_id, fin, data);
   return BuildPacket();
 }
 
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index da18b98f..446ed54 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -36,7 +36,6 @@
   if (is_clang) {
     cflags += [
       "-Wno-unused-private-field",
-      "-Wno-shadow",
       "-Wno-sign-compare",
     ]
   }
diff --git a/net/third_party/quiche/src b/net/third_party/quiche/src
index 2789c40..b05cd0f 160000
--- a/net/third_party/quiche/src
+++ b/net/third_party/quiche/src
@@ -1 +1 @@
-Subproject commit 2789c408aa2677e170a7f4f8a044e42a2f5c8bfe
+Subproject commit b05cd0f90dcbfb962056ec04263452aa14120677
diff --git a/sandbox/policy/features.cc b/sandbox/policy/features.cc
index b4c3d81..0f26ffe 100644
--- a/sandbox/policy/features.cc
+++ b/sandbox/policy/features.cc
@@ -165,12 +165,12 @@
 // Enables the renderer on Android to use a separate seccomp policy.
 BASE_FEATURE(kUseRendererProcessPolicy,
              "UseRendererProcessPolicy",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 // When enabled, this features restricts a set of syscalls in
 // BaselinePolicyAndroid that are used by RendererProcessPolicy.
 BASE_FEATURE(kRestrictRendererPoliciesInBaseline,
              "RestrictRendererPoliciesInBaseline",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 // When enabled, restrict clone to just flags used by fork and pthread_create on
 // android.
 BASE_FEATURE(kRestrictCloneParameters,
diff --git a/services/network/BUILD.gn b/services/network/BUILD.gn
index a996cce..fd8cce9 100644
--- a/services/network/BUILD.gn
+++ b/services/network/BUILD.gn
@@ -62,6 +62,8 @@
     "ip_protection/ip_protection_config_cache.h",
     "ip_protection/ip_protection_config_cache_impl.cc",
     "ip_protection/ip_protection_config_cache_impl.h",
+    "ip_protection/ip_protection_proxy_delegate.cc",
+    "ip_protection/ip_protection_proxy_delegate.h",
     "ip_protection/ip_protection_proxy_list_manager.h",
     "ip_protection/ip_protection_proxy_list_manager_impl.cc",
     "ip_protection/ip_protection_proxy_list_manager_impl.h",
@@ -429,6 +431,7 @@
     "http_cache_data_remover_unittest.cc",
     "ignore_errors_cert_verifier_unittest.cc",
     "ip_protection/ip_protection_config_cache_impl_unittest.cc",
+    "ip_protection/ip_protection_proxy_delegate_unittest.cc",
     "ip_protection/ip_protection_proxy_list_manager_impl_unittest.cc",
     "ip_protection/ip_protection_token_cache_manager_impl_unittest.cc",
     "keepalive_statistics_recorder_unittest.cc",
diff --git a/services/network/ip_protection/ip_protection_proxy_delegate.cc b/services/network/ip_protection/ip_protection_proxy_delegate.cc
new file mode 100644
index 0000000..4ccfe3bb8
--- /dev/null
+++ b/services/network/ip_protection/ip_protection_proxy_delegate.cc
@@ -0,0 +1,199 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/ip_protection/ip_protection_proxy_delegate.h"
+
+#include "base/containers/contains.h"
+#include "base/feature_list.h"
+#include "base/functional/bind.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/strings/strcat.h"
+#include "net/base/features.h"
+#include "net/base/proxy_chain.h"
+#include "net/base/proxy_server.h"
+#include "net/base/url_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_util.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "net/proxy_resolution/proxy_resolution_service.h"
+#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
+#include "services/network/url_loader.h"
+#include "url/url_constants.h"
+
+namespace network {
+
+IpProtectionProxyDelegate::IpProtectionProxyDelegate(
+    NetworkServiceProxyAllowList* network_service_proxy_allow_list)
+    : network_service_proxy_allow_list_(network_service_proxy_allow_list) {}
+
+IpProtectionProxyDelegate::~IpProtectionProxyDelegate() = default;
+
+void IpProtectionProxyDelegate::OnResolveProxy(
+    const GURL& url,
+    const net::NetworkAnonymizationKey& network_anonymization_key,
+    const std::string& method,
+    const net::ProxyRetryInfoMap& proxy_retry_info,
+    net::ProxyInfo* result) {
+  auto dvlog = [&](std::string message) {
+    absl::optional<net::SchemefulSite> top_frame_site =
+        network_anonymization_key.GetTopFrameSite();
+    DVLOG(3) << "NSPD::OnResolveProxy(" << url << ", "
+             << (top_frame_site.has_value() ? top_frame_site.value()
+                                            : net::SchemefulSite())
+             << ") - " << message;
+  };
+  // Note: We do not proxy requests if:
+  // - The allow list is not available or is not enabled.
+  // - The request doesn't match the allow list.
+  // - The token cache is not available.
+  // - The token cache does not have tokens.
+  // - No proxy list is available.
+  // - `kEnableIpProtection` is `false`.
+  // - `kIpPrivacyDirectOnly` is `true`.
+  if (!network_service_proxy_allow_list_) {
+    dvlog("no proxy allow list");
+    return;
+  }
+  if (!network_service_proxy_allow_list_->IsEnabled()) {
+    dvlog("proxy allow list not enabled");
+    return;
+  }
+  if (!network_service_proxy_allow_list_->Matches(url,
+                                                  network_anonymization_key)) {
+    dvlog("proxy allow list did not match");
+    return;
+  }
+  result->set_is_mdl_match(true);
+  if (!base::FeatureList::IsEnabled(net::features::kEnableIpProtectionProxy)) {
+    dvlog("ip protection proxy not enabled");
+    return;
+  }
+  if (!ipp_config_cache_) {
+    dvlog("no cache");
+    return;
+  }
+  if (!ipp_config_cache_->AreAuthTokensAvailable()) {
+    dvlog("no auth token available from cache");
+    return;
+  }
+  if (!ipp_config_cache_->IsProxyListAvailable()) {
+    // NOTE: When this `vlog()` is removed, there's no need to distinguish
+    // the case where a proxy list has not been downloaded, and the case
+    // where a proxy list is empty. The `IsProxyListAvailable()` method can
+    // be removed at that time.
+    dvlog("no proxy list available from cache");
+    return;
+  }
+
+  net::ProxyList proxy_list;
+  if (!net::features::kIpPrivacyDirectOnly.Get()) {
+    const std::vector<net::ProxyChain>& proxy_chain_list =
+        ipp_config_cache_->GetProxyChainList();
+    for (const auto& proxy_chain : proxy_chain_list) {
+      if (proxy_chain.is_single_proxy() && url.SchemeIs(url::kHttpScheme)) {
+        // Proxying HTTP traffic correctly for IP Protection requires
+        // multi-proxy chains to be used, so if a single-proxy chain is
+        // encountered here then just fail.
+        // TODO(https://crbug.com/1474932): Once chains are guaranteed to be
+        // multi-proxy here, turn this into a CHECK.
+        dvlog("can't proxy HTTP URL through a single-proxy chain");
+        return;
+      }
+      proxy_list.AddProxyChain(std::move(proxy_chain));
+    }
+  }
+  // Final fallback is to DIRECT.
+  auto direct_proxy_chain = net::ProxyChain::Direct();
+  if (net::features::kIpPrivacyDirectOnly.Get()) {
+    // To enable measuring how much traffic would be proxied (for
+    // experimentation and planning purposes), mark the direct
+    // proxy chain as being for IP Protection when `kIpPrivacyDirectOnly` is
+    // true. When it is false, we only care about traffic that actually went
+    // through the IP Protection proxies, so don't set this flag.
+    direct_proxy_chain = std::move(direct_proxy_chain).ForIpProtection();
+  }
+  proxy_list.AddProxyChain(std::move(direct_proxy_chain));
+
+  if (VLOG_IS_ON(3)) {
+    dvlog(base::StrCat({"setting proxy list (before deprioritization) to ",
+                        proxy_list.ToDebugString()}));
+  }
+  result->OverrideProxyList(MergeProxyRules(result->proxy_list(), proxy_list));
+  result->DeprioritizeBadProxyChains(proxy_retry_info);
+  return;
+}
+
+void IpProtectionProxyDelegate::OnFallback(const net::ProxyChain& bad_chain,
+                                           int net_error) {
+  // If the bad proxy was an IP Protection proxy, refresh the list of IP
+  // protection proxies immediately.
+  if (bad_chain.is_for_ip_protection()) {
+    CHECK(ipp_config_cache_);
+    ipp_config_cache_->RequestRefreshProxyList();
+  }
+}
+
+void IpProtectionProxyDelegate::OnBeforeTunnelRequest(
+    const net::ProxyChain& proxy_chain,
+    size_t chain_index,
+    net::HttpRequestHeaders* extra_headers) {
+  auto vlog = [](std::string message) {
+    VLOG(2) << "NSPD::OnBeforeTunnelRequest() - " << message;
+  };
+  if (proxy_chain.is_for_ip_protection()) {
+    // Temporarily support a pre-shared key for access to proxyB.
+    if (chain_index == 1) {
+      std::string proxy_b_psk = net::features::kIpPrivacyProxyBPsk.Get();
+      if (!proxy_b_psk.empty()) {
+        vlog("adding proxyB PSK");
+        extra_headers->SetHeader(net::HttpRequestHeaders::kProxyAuthorization,
+                                 base::StrCat({"Preshared ", proxy_b_psk}));
+      }
+    }
+    CHECK(ipp_config_cache_);
+    absl::optional<network::mojom::BlindSignedAuthTokenPtr> token =
+        ipp_config_cache_->GetAuthToken(chain_index);
+    if (token) {
+      vlog("adding auth token");
+      // The token value we have here is the full Authorization header value, so
+      // we can add it verbatim.
+      extra_headers->SetHeader(net::HttpRequestHeaders::kAuthorization,
+                               std::move((*token)->token));
+    } else {
+      vlog("no token available");
+    }
+  } else {
+    vlog("not for IP protection");
+  }
+}
+
+net::Error IpProtectionProxyDelegate::OnTunnelHeadersReceived(
+    const net::ProxyChain& proxy_chain,
+    size_t chain_index,
+    const net::HttpResponseHeaders& response_headers) {
+  return net::OK;
+}
+
+void IpProtectionProxyDelegate::SetProxyResolutionService(
+    net::ProxyResolutionService* proxy_resolution_service) {}
+
+net::ProxyList IpProtectionProxyDelegate::MergeProxyRules(
+    const net::ProxyList& existing_proxy_list,
+    const net::ProxyList& custom_proxy_list) const {
+  net::ProxyList merged_proxy_list;
+  for (const auto& existing_chain : existing_proxy_list.AllChains()) {
+    if (existing_chain.is_direct()) {
+      // Replace direct option with all proxies in the custom proxy list
+      for (const auto& custom_chain : custom_proxy_list.AllChains()) {
+        merged_proxy_list.AddProxyChain(custom_chain);
+      }
+    } else {
+      merged_proxy_list.AddProxyChain(existing_chain);
+    }
+  }
+
+  return merged_proxy_list;
+}
+
+}  // namespace network
diff --git a/services/network/ip_protection/ip_protection_proxy_delegate.h b/services/network/ip_protection/ip_protection_proxy_delegate.h
new file mode 100644
index 0000000..e7ee8ad
--- /dev/null
+++ b/services/network/ip_protection/ip_protection_proxy_delegate.h
@@ -0,0 +1,83 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_NETWORK_IP_PROTECTION_IP_PROTECTION_PROXY_DELEGATE_H_
+#define SERVICES_NETWORK_IP_PROTECTION_IP_PROTECTION_PROXY_DELEGATE_H_
+
+#include <deque>
+
+#include "base/component_export.h"
+#include "base/memory/raw_ptr.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/proxy_delegate.h"
+#include "services/network/ip_protection/ip_protection_config_cache.h"
+#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+
+namespace net {
+class HttpRequestHeaders;
+class ProxyResolutionService;
+}  // namespace net
+
+namespace network {
+
+// IpProtectionProxyDelegate is used to support IP protection, by injecting
+// proxies for requests where IP should be protected.
+class COMPONENT_EXPORT(NETWORK_SERVICE) IpProtectionProxyDelegate
+    : public net::ProxyDelegate {
+ public:
+  IpProtectionProxyDelegate(
+      NetworkServiceProxyAllowList* network_service_proxy_allow_list);
+
+  IpProtectionProxyDelegate(const IpProtectionProxyDelegate&) = delete;
+  IpProtectionProxyDelegate& operator=(const IpProtectionProxyDelegate&) =
+      delete;
+
+  ~IpProtectionProxyDelegate() override;
+
+  void SetIpProtectionConfigCache(
+      std::unique_ptr<IpProtectionConfigCache> ipp_config_cache) {
+    ipp_config_cache_ = std::move(ipp_config_cache);
+  }
+
+  // net::ProxyDelegate implementation:
+  void OnResolveProxy(
+      const GURL& url,
+      const net::NetworkAnonymizationKey& network_anonymization_key,
+      const std::string& method,
+      const net::ProxyRetryInfoMap& proxy_retry_info,
+      net::ProxyInfo* result) override;
+  void OnFallback(const net::ProxyChain& bad_chain, int net_error) override;
+  void OnBeforeTunnelRequest(const net::ProxyChain& proxy_chain,
+                             size_t chain_index,
+                             net::HttpRequestHeaders* extra_headers) override;
+  net::Error OnTunnelHeadersReceived(
+      const net::ProxyChain& proxy_chain,
+      size_t chain_index,
+      const net::HttpResponseHeaders& response_headers) override;
+  void SetProxyResolutionService(
+      net::ProxyResolutionService* proxy_resolution_service) override;
+
+  IpProtectionConfigCache* GetIpProtectionConfigCache() {
+    return ipp_config_cache_.get();
+  }
+
+ private:
+  friend class IpProtectionProxyDelegateTest;
+  FRIEND_TEST_ALL_PREFIXES(IpProtectionProxyDelegateTest, MergeProxyRules);
+
+  // Returns the equivalent of replacing all DIRECT proxies in
+  // `existing_proxy_list` with the proxies in `custom_proxy_list`.
+  net::ProxyList MergeProxyRules(const net::ProxyList& existing_proxy_list,
+                                 const net::ProxyList& custom_proxy_list) const;
+
+  const raw_ptr<NetworkServiceProxyAllowList> network_service_proxy_allow_list_;
+
+  std::unique_ptr<IpProtectionConfigCache> ipp_config_cache_;
+};
+
+}  // namespace network
+
+#endif  // SERVICES_NETWORK_IP_PROTECTION_IP_PROTECTION_PROXY_DELEGATE_H_
diff --git a/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc b/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc
new file mode 100644
index 0000000..d537c29e
--- /dev/null
+++ b/services/network/ip_protection/ip_protection_proxy_delegate_unittest.cc
@@ -0,0 +1,725 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/network/ip_protection/ip_protection_proxy_delegate.h"
+
+#include <string>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "net/base/network_anonymization_key.h"
+#include "net/base/proxy_chain.h"
+#include "net/base/proxy_string_util.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/ip_protection/ip_protection_config_cache_impl.h"
+#include "services/network/ip_protection/ip_protection_proxy_list_manager.h"
+#include "services/network/ip_protection/ip_protection_token_cache_manager.h"
+#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
+#include "services/network/public/cpp/features.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace network {
+namespace {
+
+constexpr char kHttpsUrl[] = "https://example.com";
+constexpr char kHttpUrl[] = "http://example.com";
+constexpr char kLocalhost[] = "http://localhost";
+
+class MockIpProtectionConfigCache : public IpProtectionConfigCache {
+ public:
+  bool AreAuthTokensAvailable() override { return auth_token_.has_value(); }
+  void InvalidateTryAgainAfterTime() override {}
+  absl::optional<network::mojom::BlindSignedAuthTokenPtr> GetAuthToken(
+      size_t chain_index) override {
+    return std::move(auth_token_);
+  }
+
+  // Set the auth token that will be returned from the next call to
+  // `GetAuthToken()`.
+  void SetNextAuthToken(
+      absl::optional<network::mojom::BlindSignedAuthTokenPtr> auth_token) {
+    auth_token_ = std::move(auth_token);
+  }
+
+  void SetUp() override { NOTREACHED_NORETURN(); }
+
+  void SetIpProtectionProxyListManagerForTesting(
+      std::unique_ptr<IpProtectionProxyListManager> ipp_proxy_list_manager)
+      override {
+    NOTREACHED_NORETURN();
+  }
+
+  IpProtectionTokenCacheManager* GetIpProtectionTokenCacheManagerForTesting(
+      network::mojom::IpProtectionProxyLayer proxy_layer) override {
+    NOTREACHED_NORETURN();
+  }
+
+  void SetIpProtectionTokenCacheManagerForTesting(
+      network::mojom::IpProtectionProxyLayer proxy_layer,
+      std::unique_ptr<IpProtectionTokenCacheManager> ipp_token_cache_manager)
+      override {
+    NOTREACHED_NORETURN();
+  }
+
+  std::vector<net::ProxyChain> GetProxyChainList() override {
+    return proxy_chain_list_;
+  }
+
+  bool IsProxyListAvailable() override { return proxy_list_.has_value(); }
+
+  void RequestRefreshProxyList() override {
+    if (on_force_refresh_proxy_list_) {
+      std::move(on_force_refresh_proxy_list_).Run();
+    }
+  }
+
+  // Set the proxy list returned from `ProxyList()`.
+  void SetProxyList(std::vector<std::vector<std::string>> proxy_list) {
+    proxy_list_ = std::move(proxy_list);
+    proxy_chain_list_ = IpProtectionConfigCacheImpl::
+        ConvertProxyServerStringsToProxyChainListForTesting(*proxy_list_);
+  }
+
+  void SetOnRequestRefreshProxyList(
+      base::OnceClosure on_force_refresh_proxy_list) {
+    on_force_refresh_proxy_list_ = std::move(on_force_refresh_proxy_list);
+  }
+
+ private:
+  absl::optional<network::mojom::BlindSignedAuthTokenPtr> auth_token_;
+  absl::optional<std::vector<std::vector<std::string>>> proxy_list_;
+  std::vector<net::ProxyChain> proxy_chain_list_;
+  base::OnceClosure on_force_refresh_proxy_list_;
+};
+
+}  // namespace
+
+MATCHER_P2(Contain,
+           expected_name,
+           expected_value,
+           std::string("headers ") + (negation ? "don't " : "") + "contain '" +
+               expected_name + ": " + expected_value + "'") {
+  std::string value;
+  return arg.GetHeader(expected_name, &value) && value == expected_value;
+}
+
+struct HeadersReceived {
+  net::ProxyChain proxy_chain;
+  uint64_t chain_index;
+  scoped_refptr<net::HttpResponseHeaders> response_headers;
+};
+
+class TestCustomProxyConnectionObserver
+    : public mojom::CustomProxyConnectionObserver {
+ public:
+  TestCustomProxyConnectionObserver() = default;
+  ~TestCustomProxyConnectionObserver() override = default;
+
+  const absl::optional<std::pair<net::ProxyChain, int>>& FallbackArgs() const {
+    return fallback_;
+  }
+
+  const absl::optional<HeadersReceived>& HeadersReceivedArgs() const {
+    return headers_received_;
+  }
+
+  // mojom::CustomProxyConnectionObserver:
+  void OnFallback(const net::ProxyChain& bad_chain, int net_error) override {
+    fallback_ = std::make_pair(bad_chain, net_error);
+  }
+  void OnTunnelHeadersReceived(const net::ProxyChain& proxy_chain,
+                               uint64_t chain_index,
+                               const scoped_refptr<net::HttpResponseHeaders>&
+                                   response_headers) override {
+    headers_received_ =
+        HeadersReceived{proxy_chain, chain_index, response_headers};
+  }
+
+ private:
+  absl::optional<std::pair<net::ProxyChain, int>> fallback_;
+  absl::optional<HeadersReceived> headers_received_;
+};
+
+class IpProtectionProxyDelegateTest : public testing::Test {
+ public:
+  IpProtectionProxyDelegateTest() = default;
+
+  void SetUp() override {
+    context_ = net::CreateTestURLRequestContextBuilder()->Build();
+    scoped_feature_list_.InitWithFeatures(
+        {net::features::kEnableIpProtectionProxy,
+         network::features::kMaskedDomainList},
+        {});
+  }
+
+ protected:
+  std::unique_ptr<IpProtectionProxyDelegate> CreateDelegate(
+      NetworkServiceProxyAllowList* network_service_proxy_allow_list) {
+    return std::make_unique<IpProtectionProxyDelegate>(
+        network_service_proxy_allow_list);
+  }
+
+  std::unique_ptr<net::URLRequest> CreateRequest(const GURL& url) {
+    return context_->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr,
+                                   TRAFFIC_ANNOTATION_FOR_TESTS);
+  }
+
+  mojom::BlindSignedAuthTokenPtr MakeAuthToken(std::string content) {
+    auto token = mojom::BlindSignedAuthToken::New();
+    token->token = std::move(content);
+    return token;
+  }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+ private:
+  std::unique_ptr<net::URLRequestContext> context_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(IpProtectionProxyDelegateTest, AddsTokenToTunnelRequest) {
+  auto delegate = CreateDelegate(nullptr);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxya", "proxyb"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::HttpRequestHeaders headers;
+  auto ip_protection_proxy_chain =
+      net::ProxyChain(
+          {net::ProxyServer::FromSchemeHostAndPort(
+               net::ProxyServer::SCHEME_HTTPS, "proxya", absl::nullopt),
+           net::ProxyServer::FromSchemeHostAndPort(
+               net::ProxyServer::SCHEME_HTTPS, "proxyb", absl::nullopt)})
+          .ForIpProtection();
+  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/0,
+                                  &headers);
+
+  EXPECT_THAT(headers, Contain("Authorization", "Bearer: a-token"));
+}
+
+TEST_F(IpProtectionProxyDelegateTest, AddsPskToTunnelRequest) {
+  std::map<std::string, std::string> parameters;
+  parameters[net::features::kIpPrivacyProxyBPsk.name] = "seekrit";
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      net::features::kEnableIpProtectionProxy, std::move(parameters));
+
+  auto delegate = CreateDelegate(nullptr);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetProxyList({{"proxya", "proxyb"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::HttpRequestHeaders headers;
+  auto ip_protection_proxy_chain =
+      net::ProxyChain(
+          {net::ProxyServer::FromSchemeHostAndPort(
+               net::ProxyServer::SCHEME_HTTPS, "proxya", absl::nullopt),
+           net::ProxyServer::FromSchemeHostAndPort(
+               net::ProxyServer::SCHEME_HTTPS, "proxyb", absl::nullopt)})
+          .ForIpProtection();
+  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/0,
+                                  &headers);
+  EXPECT_THAT(headers, testing::Not(Contain("Proxy-Authorization",
+                                            "Preshared seekrit")));
+
+  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/1,
+                                  &headers);
+  EXPECT_THAT(headers, Contain("Proxy-Authorization", "Preshared seekrit"));
+}
+
+TEST_F(IpProtectionProxyDelegateTest,
+       OnResolveProxyDiscardsInvalidProxyServers) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"[foo]"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxyDeprioritizesBadProxies) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxya"}, {"backup"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyRetryInfoMap retry_map;
+  net::ProxyRetryInfo& info =
+      retry_map[ProxyUriToProxyChain("https://proxya",
+                                     net::ProxyServer::SCHEME_HTTPS)
+                    .ForIpProtection()];
+  info.try_while_bad = false;
+  info.bad_until = base::TimeTicks::Now() + base::Days(2);
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", std::move(retry_map), &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyChain(
+      net::PacResultElementToProxyChain("HTTPS backup").ForIpProtection());
+  expected_proxy_list.AddProxyServer(net::ProxyServer::Direct());
+
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list))
+      << "Got: " << result.proxy_list().ToDebugString();
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxyAllProxiesBad) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxya"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyRetryInfoMap retry_map;
+  net::ProxyRetryInfo& info =
+      retry_map[ProxyUriToProxyChain("https://proxya",
+                                     net::ProxyServer::SCHEME_HTTPS)
+                    .ForIpProtection()];
+  info.try_while_bad = false;
+  info.bad_until = base::TimeTicks::Now() + base::Days(2);
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", std::move(retry_map), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest,
+       OnResolveProxyNetworkServiceProxyAllowListMatch) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList(
+      {{"ippro-1", "ippro-2"}, {"ippro-2", "ippro-2"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  // Verify that the IP Protection proxy list is correctly merged with the
+  // existing proxy list.
+  result.UsePacString("PROXY bar; DIRECT; PROXY weird");
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  net::ProxyList expected_proxy_list;
+  expected_proxy_list.AddProxyServer(
+      net::PacResultElementToProxyServer("PROXY bar"));
+
+  const net::ProxyServer kProxyServer1{net::ProxyServer::SCHEME_HTTPS,
+                                       net::HostPortPair("ippro-1", 443)};
+  const net::ProxyServer kProxyServer2{net::ProxyServer::SCHEME_HTTPS,
+                                       net::HostPortPair("ippro-2", 443)};
+  const net::ProxyChain kIpProtectionChain1 =
+      net::ProxyChain({kProxyServer1, kProxyServer2}).ForIpProtection();
+  const net::ProxyChain kIpProtectionChain2 =
+      net::ProxyChain({kProxyServer2, kProxyServer2}).ForIpProtection();
+
+  expected_proxy_list.AddProxyChain(std::move(kIpProtectionChain1));
+  expected_proxy_list.AddProxyChain(std::move(kIpProtectionChain2));
+  expected_proxy_list.AddProxyServer(net::ProxyServer::Direct());
+  expected_proxy_list.AddProxyServer(
+      net::PacResultElementToProxyServer("PROXY weird"));
+
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list))
+      << "Got: " << result.proxy_list().ToDebugString();
+  EXPECT_FALSE(result.is_for_ip_protection());
+
+  // After a fallback, the first IP Protection proxy chain should be used.
+  EXPECT_TRUE(result.Fallback(net::ERR_PROXY_CONNECTION_FAILED,
+                              net::NetLogWithSource()));
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest,
+       OnResolveProxyNetworkServiceProxyAllowListMatch_DirectOnly) {
+  std::map<std::string, std::string> parameters;
+  parameters[net::features::kIpPrivacyDirectOnly.name] = "true";
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      net::features::kEnableIpProtectionProxy, std::move(parameters));
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"foo"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  net::ProxyList expected_proxy_list;
+  auto ip_protection_proxy_chain = net::ProxyChain::Direct().ForIpProtection();
+  expected_proxy_list.AddProxyChain(std::move(ip_protection_proxy_chain));
+  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list))
+      << "Got: " << result.proxy_list().ToDebugString();
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
+TEST_F(
+    IpProtectionProxyDelegateTest,
+    OnResolveProxyNetworkServiceProxyAllowListDoesNotMatch_FirstPartyException) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {"top.com"};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxy_NoConfigCache) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxy_NoAuthToken) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetProxyList({{"proxy"}});
+  // No token is added to the cache, so the result will be direct.
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxy_NoProxyList) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  // No proxy list is added to the cache, so the result will be direct.
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxy_AllowListDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures({},
+                                       {net::features::kEnableIpProtectionProxy,
+                                        network::features::kMaskedDomainList});
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxy"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+TEST_F(
+    IpProtectionProxyDelegateTest,
+    OnResolveProxyNetworkServiceProxyAllowListDoesNotMatch_ResourceNotAllowed) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+// When URLs do not match the allow list, the result is direct and not flagged
+// as for IP protection.
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxyIpProtectionNoMatch) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kLocalhost),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("http://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+// When the URL is HTTP and single-proxy chains are used, the result is direct
+// and not flagged as for IP Protection.
+// TODO(https://crbug.com/1474932): Once IP Protection chains are guaranteed to
+// be multi-proxy, we can remove this test.
+TEST_F(IpProtectionProxyDelegateTest,
+       OnResolveProxyIpProtectionSingleProxyHttpFailure) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxy"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("http://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_TRUE(result.is_direct());
+  EXPECT_FALSE(result.is_for_ip_protection());
+}
+
+// When the URL is HTTP and multi-proxy chains are used, the result is flagged
+// as for IP protection and is not direct.
+TEST_F(IpProtectionProxyDelegateTest,
+       OnResolveProxyIpProtectionMultiProxyHttpSuccess) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxy1", "proxy2"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("http://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_FALSE(result.is_direct());
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
+// When URLs match the allow list, and a token is available, the result is
+// flagged as for IP protection and is not direct.
+TEST_F(IpProtectionProxyDelegateTest, OnResolveProxyIpProtectionHttpsSuccess) {
+  std::map<std::string, std::set<std::string>> first_party_map;
+  first_party_map["example.com"] = {};
+  auto network_service_proxy_allow_list =
+      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
+  auto delegate = CreateDelegate(&network_service_proxy_allow_list);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
+  ipp_config_cache->SetProxyList({{"proxy"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  net::ProxyInfo result;
+  result.UseDirect();
+  delegate->OnResolveProxy(GURL(kHttpsUrl),
+                           net::NetworkAnonymizationKey::CreateCrossSite(
+                               net::SchemefulSite(GURL("https://top.com"))),
+                           "GET", net::ProxyRetryInfoMap(), &result);
+  EXPECT_FALSE(result.is_direct());
+  EXPECT_TRUE(result.is_for_ip_protection());
+}
+
+TEST_F(IpProtectionProxyDelegateTest, OnFallback_IpProtection) {
+  auto ip_protection_proxy_chain =
+      net::ProxyChain::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                             "proxy.com", absl::nullopt)
+          .ForIpProtection();
+  bool force_refresh_called = false;
+
+  auto delegate = CreateDelegate(nullptr);
+
+  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
+  ipp_config_cache->SetOnRequestRefreshProxyList(
+      base::BindLambdaForTesting([&]() { force_refresh_called = true; }));
+  ipp_config_cache->SetProxyList({{"proxy.com"}});
+  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
+
+  delegate->OnFallback(ip_protection_proxy_chain, net::ERR_FAILED);
+  EXPECT_TRUE(force_refresh_called);
+}
+
+TEST_F(IpProtectionProxyDelegateTest, MergeProxyRules) {
+  net::ProxyChain chain1({
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "proxy2a.com", 80),
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "proxy2b.com", 80),
+  });
+  net::ProxyChain chain2(net::ProxyServer::Direct());
+  net::ProxyChain chain3({
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "proxy1.com", 80),
+  });
+  net::ProxyList existing_proxy_list;
+  existing_proxy_list.AddProxyChain(chain1);
+  existing_proxy_list.AddProxyChain(chain2);
+  existing_proxy_list.AddProxyChain(chain3);
+
+  net::ProxyChain custom1({
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "custom-a.com", 80),
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "custom-b.com", 80),
+      net::ProxyServer::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
+                                              "custom-c.com", 80),
+  });
+  net::ProxyChain custom2(net::ProxyServer::Direct());
+  net::ProxyList custom_proxy_list;
+  custom_proxy_list.AddProxyChain(custom1);
+  custom_proxy_list.AddProxyChain(custom2);
+
+  auto delegate = CreateDelegate(nullptr);
+
+  auto result =
+      delegate->MergeProxyRules(existing_proxy_list, custom_proxy_list);
+
+  // Custom chains replace `chain2`.
+  std::vector<net::ProxyChain> expected = {
+      chain1,
+      custom1,
+      custom2,
+      chain3,
+  };
+  EXPECT_EQ(result.AllChains(), expected);
+}
+
+}  // namespace network
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index dc72b94..06e53dcec8 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -98,6 +98,7 @@
 #include "services/network/http_server_properties_pref_delegate.h"
 #include "services/network/ignore_errors_cert_verifier.h"
 #include "services/network/ip_protection/ip_protection_config_cache_impl.h"
+#include "services/network/ip_protection/ip_protection_proxy_delegate.h"
 #include "services/network/ip_protection/ip_protection_token_cache_manager_impl.h"
 #include "services/network/is_browser_initiated.h"
 #include "services/network/net_log_exporter.h"
@@ -1932,7 +1933,11 @@
   // initialized.
   CHECK(proxy_delegate_);
 
-  auto* ipp_config_cache = proxy_delegate_->GetIpProtectionConfigCache();
+  // TODO(crbug.com/1476881): this mojom method should move to the
+  // IpProtectionProxyDelegate class.
+  IpProtectionProxyDelegate* proxy_delegate =
+      static_cast<IpProtectionProxyDelegate*>(proxy_delegate_.get());
+  auto* ipp_config_cache = proxy_delegate->GetIpProtectionConfigCache();
   CHECK(ipp_config_cache);
   auto* ipp_token_cache_manager_impl =
       static_cast<IpProtectionTokenCacheManagerImpl*>(
@@ -1951,13 +1956,14 @@
     ipp_token_cache_manager_impl->DisableCacheManagementForTesting(  // IN-TEST
         base::BindOnce(
             [](base::WeakPtr<NetworkContext> weak_ptr,
-               VerifyIpProtectionConfigGetterForTestingCallback callback) {
+               VerifyIpProtectionConfigGetterForTestingCallback callback,
+               IpProtectionProxyDelegate* proxy_delegate) {
               // If this callback is called then `ipp_config_cache` is
               // still alive, which means that this `NetworkContext` is alive as
               // well.
               CHECK(weak_ptr);
               auto* ipp_config_cache =
-                  weak_ptr->proxy_delegate_->GetIpProtectionConfigCache();
+                  proxy_delegate->GetIpProtectionConfigCache();
               ipp_config_cache->InvalidateTryAgainAfterTime();
               while (ipp_config_cache->AreAuthTokensAvailable()) {
                 ipp_config_cache->GetAuthToken(0);  // kProxyA.
@@ -1973,7 +1979,7 @@
                       &NetworkContext::VerifyIpProtectionConfigGetterForTesting,
                       weak_ptr, std::move(callback)));
             },
-            weak_factory_.GetWeakPtr(), std::move(callback)));
+            weak_factory_.GetWeakPtr(), std::move(callback), proxy_delegate));
     return;
   }
 
@@ -1995,9 +2001,12 @@
   ipp_token_cache_manager_impl->CallTryGetAuthTokensForTesting();  // IN-TEST
 }
 
+// TODO(crbug.com/1476881): this should move to IpProtectionProxyDelegate
 void NetworkContext::OnIpProtectionConfigAvailableForTesting(
     VerifyIpProtectionConfigGetterForTestingCallback callback) {
-  auto* ipp_config_cache = proxy_delegate_->GetIpProtectionConfigCache();
+  IpProtectionProxyDelegate* proxy_delegate =
+      static_cast<IpProtectionProxyDelegate*>(proxy_delegate_.get());
+  auto* ipp_config_cache = proxy_delegate->GetIpProtectionConfigCache();
   auto* ipp_token_cache_manager_impl =
       static_cast<IpProtectionTokenCacheManagerImpl*>(
           ipp_config_cache
@@ -2020,7 +2029,9 @@
   if (!proxy_delegate_) {
     return;
   }
-  auto* ipp_config_cache = proxy_delegate_->GetIpProtectionConfigCache();
+  IpProtectionProxyDelegate* proxy_delegate =
+      static_cast<IpProtectionProxyDelegate*>(proxy_delegate_.get());
+  auto* ipp_config_cache = proxy_delegate->GetIpProtectionConfigCache();
   if (!ipp_config_cache) {
     return;
   }
@@ -2359,13 +2370,13 @@
   network_delegate_ = network_delegate.get();
   builder.set_network_delegate(std::move(network_delegate));
 
-  if (params_->initial_custom_proxy_config ||
-      params_->custom_proxy_config_client_receiver) {
-    std::unique_ptr<NetworkServiceProxyDelegate> proxy_delegate =
-        std::make_unique<NetworkServiceProxyDelegate>(
-            std::move(params_->initial_custom_proxy_config),
-            std::move(params_->custom_proxy_config_client_receiver),
-            std::move(params_->custom_proxy_connection_observer_remote),
+  // Decide which ProxyDelegate to create.
+  // TODO(crbug.com/1476881): clean up this logic.
+  if (params_->initial_custom_proxy_config &&
+      params_->initial_custom_proxy_config->rules
+          .restrict_to_network_service_proxy_allow_list) {
+    std::unique_ptr<IpProtectionProxyDelegate> proxy_delegate =
+        std::make_unique<IpProtectionProxyDelegate>(
             network_service_->network_service_proxy_allow_list());
     if (params_->ip_protection_config_getter) {
       proxy_delegate->SetIpProtectionConfigCache(
@@ -2375,6 +2386,15 @@
     }
     proxy_delegate_ = proxy_delegate.get();
     builder.set_proxy_delegate(std::move(proxy_delegate));
+  } else if (params_->initial_custom_proxy_config ||
+             params_->custom_proxy_config_client_receiver) {
+    std::unique_ptr<NetworkServiceProxyDelegate> proxy_delegate =
+        std::make_unique<NetworkServiceProxyDelegate>(
+            std::move(params_->initial_custom_proxy_config),
+            std::move(params_->custom_proxy_config_client_receiver),
+            std::move(params_->custom_proxy_connection_observer_remote));
+    proxy_delegate_ = proxy_delegate.get();
+    builder.set_proxy_delegate(std::move(proxy_delegate));
   }
 
   net::NetLog* net_log = nullptr;
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 5a8232b..988ef64 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -94,6 +94,7 @@
 class HostPortPair;
 class IsolationInfo;
 class NetworkAnonymizationKey;
+class ProxyDelegate;
 class StaticHttpUserAgentSettings;
 class URLRequestContext;
 class URLRequestContextBuilder;
@@ -120,7 +121,6 @@
 class NetworkService;
 class NetworkServiceMemoryCache;
 class NetworkServiceNetworkDelegate;
-class NetworkServiceProxyDelegate;
 class P2PSocketManager;
 class PendingTrustTokenStore;
 class ProxyLookupRequest;
@@ -898,7 +898,7 @@
   std::unique_ptr<net::HostResolver::ProbeRequest> doh_probes_request_;
 
   // Owned by URLRequestContext.
-  raw_ptr<NetworkServiceProxyDelegate> proxy_delegate_ = nullptr;
+  raw_ptr<net::ProxyDelegate> proxy_delegate_ = nullptr;
 
   // Used for Signed Exchange certificate verification.
   uint64_t next_cert_verify_id_ = 0;
diff --git a/services/network/network_service_proxy_delegate.cc b/services/network/network_service_proxy_delegate.cc
index d144bbf..b9652e9 100644
--- a/services/network/network_service_proxy_delegate.cc
+++ b/services/network/network_service_proxy_delegate.cc
@@ -16,7 +16,6 @@
 #include "net/http/http_util.h"
 #include "net/proxy_resolution/proxy_info.h"
 #include "net/proxy_resolution/proxy_resolution_service.h"
-#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
 #include "services/network/url_loader.h"
 #include "url/url_constants.h"
 
@@ -108,11 +107,9 @@
     mojom::CustomProxyConfigPtr initial_config,
     mojo::PendingReceiver<mojom::CustomProxyConfigClient>
         config_client_receiver,
-    mojo::PendingRemote<mojom::CustomProxyConnectionObserver> observer_remote,
-    NetworkServiceProxyAllowList* network_service_proxy_allow_list)
+    mojo::PendingRemote<mojom::CustomProxyConnectionObserver> observer_remote)
     : proxy_config_(std::move(initial_config)),
-      receiver_(this, std::move(config_client_receiver)),
-      network_service_proxy_allow_list_(network_service_proxy_allow_list) {
+      receiver_(this, std::move(config_client_receiver)) {
   // Make sure there is always a valid proxy config so we don't need to null
   // check it.
   if (!proxy_config_) {
@@ -137,98 +134,6 @@
     const std::string& method,
     const net::ProxyRetryInfoMap& proxy_retry_info,
     net::ProxyInfo* result) {
-  auto dvlog = [&](std::string message) {
-    absl::optional<net::SchemefulSite> top_frame_site =
-        network_anonymization_key.GetTopFrameSite();
-    DVLOG(3) << "NSPD::OnResolveProxy(" << url << ", "
-             << (top_frame_site.has_value() ? top_frame_site.value()
-                                            : net::SchemefulSite())
-             << ") - " << message;
-  };
-  if (IsForIpProtection()) {
-    // Note: We do not proxy requests if:
-    // - The allow list is not available or is not enabled.
-    // - The request doesn't match the allow list.
-    // - The token cache is not available.
-    // - The token cache does not have tokens.
-    // - No proxy list is available.
-    // - `kEnableIpProtection` is `false`.
-    // - `kIpPrivacyDirectOnly` is `true`.
-    if (!network_service_proxy_allow_list_) {
-      dvlog("no proxy allow list");
-      return;
-    }
-    if (!network_service_proxy_allow_list_->IsEnabled()) {
-      dvlog("proxy allow list not enabled");
-      return;
-    }
-    if (!network_service_proxy_allow_list_->Matches(
-            url, network_anonymization_key)) {
-      dvlog("proxy allow list did not match");
-      return;
-    }
-    result->set_is_mdl_match(true);
-    if (!base::FeatureList::IsEnabled(
-            net::features::kEnableIpProtectionProxy)) {
-      dvlog("ip protection proxy not enabled");
-      return;
-    }
-    if (!ipp_config_cache_) {
-      dvlog("no cache");
-      return;
-    }
-    if (!ipp_config_cache_->AreAuthTokensAvailable()) {
-      dvlog("no auth token available from cache");
-      return;
-    }
-    if (!ipp_config_cache_->IsProxyListAvailable()) {
-      // NOTE: When this `vlog()` is removed, there's no need to distinguish
-      // the case where a proxy list has not been downloaded, and the case
-      // where a proxy list is empty. The `IsProxyListAvailable()` method can
-      // be removed at that time.
-      dvlog("no proxy list available from cache");
-      return;
-    }
-
-    net::ProxyList proxy_list;
-    if (!net::features::kIpPrivacyDirectOnly.Get()) {
-      const std::vector<net::ProxyChain>& proxy_chain_list =
-          ipp_config_cache_->GetProxyChainList();
-      for (const auto& proxy_chain : proxy_chain_list) {
-        if (proxy_chain.is_single_proxy() && url.SchemeIs(url::kHttpScheme)) {
-          // Proxying HTTP traffic correctly for IP Protection requires
-          // multi-proxy chains to be used, so if a single-proxy chain is
-          // encountered here then just fail.
-          // TODO(https://crbug.com/1474932): Once chains are guaranteed to be
-          // multi-proxy here, turn this into a CHECK.
-          dvlog("can't proxy HTTP URL through a single-proxy chain");
-          return;
-        }
-        proxy_list.AddProxyChain(std::move(proxy_chain));
-      }
-    }
-    // Final fallback is to DIRECT.
-    auto direct_proxy_chain = net::ProxyChain::Direct();
-    if (net::features::kIpPrivacyDirectOnly.Get()) {
-      // To enable measuring how much traffic would be proxied (for
-      // experimentation and planning purposes), mark the direct
-      // proxy chain as being for IP Protection when `kIpPrivacyDirectOnly` is
-      // true. When it is false, we only care about traffic that actually went
-      // through the IP Protection proxies, so don't set this flag.
-      direct_proxy_chain = std::move(direct_proxy_chain).ForIpProtection();
-    }
-    proxy_list.AddProxyChain(std::move(direct_proxy_chain));
-
-    if (VLOG_IS_ON(3)) {
-      dvlog(base::StrCat({"setting proxy list (before deprioritization) to ",
-                          proxy_list.ToDebugString()}));
-    }
-    result->OverrideProxyList(
-        MergeProxyRules(result->proxy_list(), proxy_list));
-    result->DeprioritizeBadProxyChains(proxy_retry_info);
-    return;
-  }
-
   // At this point, this delegate is not supporting IP protection, so apply the
   // `proxy_config_` as usual.
   if (!EligibleForProxy(*result, method)) {
@@ -251,13 +156,6 @@
 
 void NetworkServiceProxyDelegate::OnFallback(const net::ProxyChain& bad_chain,
                                              int net_error) {
-  // If the bad proxy was an IP Protection proxy, refresh the list of IP
-  // protection proxies immediately.
-  if (bad_chain.is_for_ip_protection()) {
-    CHECK(ipp_config_cache_);
-    ipp_config_cache_->RequestRefreshProxyList();
-  }
-
   if (observer_) {
     observer_->OnFallback(bad_chain, net_error);
   }
@@ -267,38 +165,9 @@
     const net::ProxyChain& proxy_chain,
     size_t chain_index,
     net::HttpRequestHeaders* extra_headers) {
-
-  auto vlog = [](std::string message) {
-    VLOG(2) << "NSPD::OnBeforeTunnelRequest() - " << message;
-  };
   if (IsInProxyConfig(proxy_chain)) {
     MergeRequestHeaders(extra_headers, proxy_config_->connect_tunnel_headers);
   }
-  if (IsForIpProtection() && proxy_chain.is_for_ip_protection()) {
-    // Temporarily support a pre-shared key for access to proxyB.
-    if (chain_index == 1) {
-      std::string proxy_b_psk = net::features::kIpPrivacyProxyBPsk.Get();
-      if (!proxy_b_psk.empty()) {
-        vlog("adding proxyB PSK");
-        extra_headers->SetHeader(net::HttpRequestHeaders::kProxyAuthorization,
-                                 base::StrCat({"Preshared ", proxy_b_psk}));
-      }
-    }
-    CHECK(ipp_config_cache_);
-    absl::optional<network::mojom::BlindSignedAuthTokenPtr> token =
-        ipp_config_cache_->GetAuthToken(chain_index);
-    if (token) {
-      vlog("adding auth token");
-      // The token value we have here is the full Authorization header value, so
-      // we can add it verbatim.
-      extra_headers->SetHeader(net::HttpRequestHeaders::kAuthorization,
-                               std::move((*token)->token));
-    } else {
-      vlog("no token available");
-    }
-  } else {
-    vlog("not for IP protection");
-  }
 }
 
 net::Error NetworkServiceProxyDelegate::OnTunnelHeadersReceived(
@@ -373,16 +242,6 @@
   return false;
 }
 
-bool NetworkServiceProxyDelegate::MayProxyURL(const GURL& url) const {
-  return !proxy_config_->rules.empty();
-}
-
-bool NetworkServiceProxyDelegate::IsForIpProtection() {
-  // Only IP protection uses the network service proxy allow list, so this
-  // config represents IP protection if and only if the allow list is in use.
-  return proxy_config_->rules.restrict_to_network_service_proxy_allow_list;
-}
-
 bool NetworkServiceProxyDelegate::EligibleForProxy(
     const net::ProxyInfo& proxy_info,
     const std::string& method) const {
diff --git a/services/network/network_service_proxy_delegate.h b/services/network/network_service_proxy_delegate.h
index 08c8139..84350387 100644
--- a/services/network/network_service_proxy_delegate.h
+++ b/services/network/network_service_proxy_delegate.h
@@ -12,8 +12,6 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/proxy_delegate.h"
-#include "services/network/ip_protection/ip_protection_config_cache.h"
-#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 
 namespace net {
@@ -34,8 +32,8 @@
       mojom::CustomProxyConfigPtr initial_config,
       mojo::PendingReceiver<mojom::CustomProxyConfigClient>
           config_client_receiver,
-      mojo::PendingRemote<mojom::CustomProxyConnectionObserver> observer_remote,
-      NetworkServiceProxyAllowList* network_service_proxy_allow_list);
+      mojo::PendingRemote<mojom::CustomProxyConnectionObserver>
+          observer_remote);
 
   NetworkServiceProxyDelegate(const NetworkServiceProxyDelegate&) = delete;
   NetworkServiceProxyDelegate& operator=(const NetworkServiceProxyDelegate&) =
@@ -43,11 +41,6 @@
 
   ~NetworkServiceProxyDelegate() override;
 
-  void SetIpProtectionConfigCache(
-      std::unique_ptr<IpProtectionConfigCache> ipp_config_cache) {
-    ipp_config_cache_ = std::move(ipp_config_cache);
-  }
-
   // net::ProxyDelegate implementation:
   void OnResolveProxy(
       const GURL& url,
@@ -66,23 +59,13 @@
   void SetProxyResolutionService(
       net::ProxyResolutionService* proxy_resolution_service) override;
 
-  IpProtectionConfigCache* GetIpProtectionConfigCache() {
-    return ipp_config_cache_.get();
-  }
-
  private:
   friend class NetworkServiceProxyDelegateTest;
   FRIEND_TEST_ALL_PREFIXES(NetworkServiceProxyDelegateTest, MergeProxyRules);
 
-  // Checks if this CustomProxyConfig is supporting IP Protection.
-  bool IsForIpProtection();
-
   // Checks whether `proxy_chain` is present in the current proxy config.
   bool IsInProxyConfig(const net::ProxyChain& proxy_chain) const;
 
-  // Whether the current config may proxy |url|.
-  bool MayProxyURL(const GURL& url) const;
-
   // Whether the HTTP |method| with current |proxy_info| is eligible to be
   // proxied.
   bool EligibleForProxy(const net::ProxyInfo& proxy_info,
@@ -107,11 +90,8 @@
   mojom::CustomProxyConfigPtr proxy_config_;
   mojo::Receiver<mojom::CustomProxyConfigClient> receiver_;
   mojo::Remote<mojom::CustomProxyConnectionObserver> observer_;
-  raw_ptr<NetworkServiceProxyAllowList> network_service_proxy_allow_list_;
 
   raw_ptr<net::ProxyResolutionService> proxy_resolution_service_ = nullptr;
-
-  std::unique_ptr<IpProtectionConfigCache> ipp_config_cache_;
 };
 
 }  // namespace network
diff --git a/services/network/network_service_proxy_delegate_unittest.cc b/services/network/network_service_proxy_delegate_unittest.cc
index f30b9a7..bab9c9b 100644
--- a/services/network/network_service_proxy_delegate_unittest.cc
+++ b/services/network/network_service_proxy_delegate_unittest.cc
@@ -21,10 +21,6 @@
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_builder.h"
 #include "net/url_request/url_request_test_util.h"
-#include "services/network/ip_protection/ip_protection_config_cache_impl.h"
-#include "services/network/ip_protection/ip_protection_proxy_list_manager.h"
-#include "services/network/ip_protection/ip_protection_token_cache_manager.h"
-#include "services/network/masked_domain_list/network_service_proxy_allow_list.h"
 #include "services/network/public/cpp/features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -38,73 +34,6 @@
 constexpr char kHttpsUrl[] = "https://example.com";
 constexpr char kWebsocketUrl[] = "ws://example.com";
 
-class MockIpProtectionConfigCache : public IpProtectionConfigCache {
- public:
-  bool AreAuthTokensAvailable() override { return auth_token_.has_value(); }
-  void InvalidateTryAgainAfterTime() override {}
-  absl::optional<network::mojom::BlindSignedAuthTokenPtr> GetAuthToken(
-      size_t chain_index) override {
-    return std::move(auth_token_);
-  }
-
-  // Set the auth token that will be returned from the next call to
-  // `GetAuthToken()`.
-  void SetNextAuthToken(
-      absl::optional<network::mojom::BlindSignedAuthTokenPtr> auth_token) {
-    auth_token_ = std::move(auth_token);
-  }
-
-  void SetUp() override { NOTREACHED_NORETURN(); }
-
-  void SetIpProtectionProxyListManagerForTesting(
-      std::unique_ptr<IpProtectionProxyListManager> ipp_proxy_list_manager)
-      override {
-    NOTREACHED_NORETURN();
-  }
-
-  IpProtectionTokenCacheManager* GetIpProtectionTokenCacheManagerForTesting(
-      network::mojom::IpProtectionProxyLayer proxy_layer) override {
-    NOTREACHED_NORETURN();
-  }
-
-  void SetIpProtectionTokenCacheManagerForTesting(
-      network::mojom::IpProtectionProxyLayer proxy_layer,
-      std::unique_ptr<IpProtectionTokenCacheManager> ipp_token_cache_manager)
-      override {
-    NOTREACHED_NORETURN();
-  }
-
-  std::vector<net::ProxyChain> GetProxyChainList() override {
-    return proxy_chain_list_;
-  }
-
-  bool IsProxyListAvailable() override { return proxy_list_.has_value(); }
-
-  void RequestRefreshProxyList() override {
-    if (on_force_refresh_proxy_list_) {
-      std::move(on_force_refresh_proxy_list_).Run();
-    }
-  }
-
-  // Set the proxy list returned from `ProxyList()`.
-  void SetProxyList(std::vector<std::vector<std::string>> proxy_list) {
-    proxy_list_ = std::move(proxy_list);
-    proxy_chain_list_ = IpProtectionConfigCacheImpl::
-        ConvertProxyServerStringsToProxyChainListForTesting(*proxy_list_);
-  }
-
-  void SetOnRequestRefreshProxyList(
-      base::OnceClosure on_force_refresh_proxy_list) {
-    on_force_refresh_proxy_list_ = std::move(on_force_refresh_proxy_list);
-  }
-
- private:
-  absl::optional<network::mojom::BlindSignedAuthTokenPtr> auth_token_;
-  absl::optional<std::vector<std::vector<std::string>>> proxy_list_;
-  std::vector<net::ProxyChain> proxy_chain_list_;
-  base::OnceClosure on_force_refresh_proxy_list_;
-};
-
 }  // namespace
 
 MATCHER_P2(Contain,
@@ -168,12 +97,6 @@
  protected:
   std::unique_ptr<NetworkServiceProxyDelegate> CreateDelegate(
       mojom::CustomProxyConfigPtr config) {
-    return CreateDelegate(std::move(config), nullptr);
-  }
-
-  std::unique_ptr<NetworkServiceProxyDelegate> CreateDelegate(
-      mojom::CustomProxyConfigPtr config,
-      NetworkServiceProxyAllowList* network_service_proxy_allow_list) {
     std::unique_ptr<TestCustomProxyConnectionObserver> observer =
         std::make_unique<TestCustomProxyConnectionObserver>();
     observer_ = observer.get();
@@ -184,8 +107,7 @@
 
     auto delegate = std::make_unique<NetworkServiceProxyDelegate>(
         network::mojom::CustomProxyConfig::New(),
-        client_.BindNewPipeAndPassReceiver(), std::move(observer_remote),
-        network_service_proxy_allow_list);
+        client_.BindNewPipeAndPassReceiver(), std::move(observer_remote));
     SetConfig(std::move(config));
     return delegate;
   }
@@ -223,8 +145,7 @@
 TEST_F(NetworkServiceProxyDelegateTest, NullConfigDoesNotCrash) {
   mojo::Remote<mojom::CustomProxyConfigClient> client;
   auto delegate = std::make_unique<NetworkServiceProxyDelegate>(
-      nullptr, client.BindNewPipeAndPassReceiver(), mojo::NullRemote(),
-      nullptr);
+      nullptr, client.BindNewPipeAndPassReceiver(), mojo::NullRemote());
 
   net::HttpRequestHeaders headers;
   auto request = CreateRequest(GURL(kHttpUrl));
@@ -244,109 +165,6 @@
   EXPECT_THAT(headers, Contain("connect", "baz"));
 }
 
-TEST_F(NetworkServiceProxyDelegateTest, AddsTokenToTunnelRequest) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  auto delegate = CreateDelegate(std::move(config));
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"proxya", "proxyb"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::HttpRequestHeaders headers;
-  auto ip_protection_proxy_chain =
-      net::ProxyChain(
-          {net::ProxyServer::FromSchemeHostAndPort(
-               net::ProxyServer::SCHEME_HTTPS, "proxya", absl::nullopt),
-           net::ProxyServer::FromSchemeHostAndPort(
-               net::ProxyServer::SCHEME_HTTPS, "proxyb", absl::nullopt)})
-          .ForIpProtection();
-  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/0,
-                                  &headers);
-
-  EXPECT_THAT(headers, Contain("Authorization", "Bearer: a-token"));
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, AddsPskToTunnelRequest) {
-  std::map<std::string, std::string> parameters;
-  parameters[net::features::kIpPrivacyProxyBPsk.name] = "seekrit";
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy, std::move(parameters));
-
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  auto delegate = CreateDelegate(std::move(config));
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetProxyList({{"proxya", "proxyb"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::HttpRequestHeaders headers;
-  auto ip_protection_proxy_chain =
-      net::ProxyChain(
-          {net::ProxyServer::FromSchemeHostAndPort(
-               net::ProxyServer::SCHEME_HTTPS, "proxya", absl::nullopt),
-           net::ProxyServer::FromSchemeHostAndPort(
-               net::ProxyServer::SCHEME_HTTPS, "proxyb", absl::nullopt)})
-          .ForIpProtection();
-  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/0,
-                                  &headers);
-  EXPECT_THAT(headers, testing::Not(Contain("Proxy-Authorization",
-                                            "Preshared seekrit")));
-
-  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/1,
-                                  &headers);
-  EXPECT_THAT(headers, Contain("Proxy-Authorization", "Preshared seekrit"));
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, NoTokenIfNotIpProtection) {
-  auto config = mojom::CustomProxyConfig::New();
-  config->rules.ParseFromString("https://proxy");
-  auto delegate = CreateDelegate(std::move(config));
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::HttpRequestHeaders headers;
-  auto ip_protection_proxy_chain =
-      net::ProxyChain(net::PacResultElementToProxyServer("HTTPS proxy"))
-          .ForIpProtection();
-  delegate->OnBeforeTunnelRequest(ip_protection_proxy_chain, /*chain_index=*/0,
-                                  &headers);
-
-  std::string value;
-  EXPECT_FALSE(headers.GetHeader("Authorization", &value));
-}
-
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyDiscardsInvalidProxyServers) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"[foo]"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
 TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpProxy) {
   auto config = mojom::CustomProxyConfig::New();
   config->rules.ParseFromString("http=foo");
@@ -687,275 +505,10 @@
   EXPECT_FALSE(result.is_for_ip_protection());
 }
 
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyNetworkServiceProxyAllowListMatch) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList(
-      {{"ippro-1", "ippro-2"}, {"ippro-2", "ippro-2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  // Verify that the IP Protection proxy list is correctly merged with the
-  // existing proxy list.
-  result.UsePacString("PROXY bar; DIRECT; PROXY weird");
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  net::ProxyList expected_proxy_list;
-  expected_proxy_list.AddProxyServer(
-      net::PacResultElementToProxyServer("PROXY bar"));
-
-  const net::ProxyServer kProxyServer1{net::ProxyServer::SCHEME_HTTPS,
-                                       net::HostPortPair("ippro-1", 443)};
-  const net::ProxyServer kProxyServer2{net::ProxyServer::SCHEME_HTTPS,
-                                       net::HostPortPair("ippro-2", 443)};
-  const net::ProxyChain kIpProtectionChain1 =
-      net::ProxyChain({kProxyServer1, kProxyServer2}).ForIpProtection();
-  const net::ProxyChain kIpProtectionChain2 =
-      net::ProxyChain({kProxyServer2, kProxyServer2}).ForIpProtection();
-
-  expected_proxy_list.AddProxyChain(std::move(kIpProtectionChain1));
-  expected_proxy_list.AddProxyChain(std::move(kIpProtectionChain2));
-  expected_proxy_list.AddProxyServer(net::ProxyServer::Direct());
-  expected_proxy_list.AddProxyServer(
-      net::PacResultElementToProxyServer("PROXY weird"));
-
-  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list))
-      << "Got: " << result.proxy_list().ToDebugString();
-  EXPECT_FALSE(result.is_for_ip_protection());
-
-  // After a fallback, the first IP Protection proxy chain should be used.
-  EXPECT_TRUE(result.Fallback(net::ERR_PROXY_CONNECTION_FAILED,
-                              net::NetLogWithSource()));
-  EXPECT_TRUE(result.is_for_ip_protection());
-}
-
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyNetworkServiceProxyAllowListMatch_DirectOnly) {
-  std::map<std::string, std::string> parameters;
-  parameters[net::features::kIpPrivacyDirectOnly.name] = "true";
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeatureWithParameters(
-      net::features::kEnableIpProtectionProxy, std::move(parameters));
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"foo"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  net::ProxyList expected_proxy_list;
-  auto ip_protection_proxy_chain = net::ProxyChain::Direct().ForIpProtection();
-  expected_proxy_list.AddProxyChain(std::move(ip_protection_proxy_chain));
-  EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list))
-      << "Got: " << result.proxy_list().ToDebugString();
-  EXPECT_TRUE(result.is_for_ip_protection());
-}
-
-TEST_F(
-    NetworkServiceProxyDelegateTest,
-    OnResolveProxyNetworkServiceProxyAllowListDoesNotMatch_FirstPartyException) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {"top.com"};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxy_NoConfigCache) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxy_NoAuthToken) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetProxyList({{"proxy"}});
-  // No token is added to the cache, so the result will be direct.
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxy_NoProxyList) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  // No proxy list is added to the cache, so the result will be direct.
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxy_AllowListDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures({},
-                                       {net::features::kEnableIpProtectionProxy,
-                                        network::features::kMaskedDomainList});
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"proxy"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-TEST_F(
-    NetworkServiceProxyDelegateTest,
-    OnResolveProxyNetworkServiceProxyAllowListDoesNotMatch_ResourceNotAllowed) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-
-  std::map<std::string, std::set<std::string>> first_party_map;
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-// When a `config` does not look like an IP Protection `CustomProxyConfig`, the
-// result is direct and not flagged as for IP Protection.
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyIpProtectionDisabledByConfig) {
+TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyNothingMatching) {
   auto config = mojom::CustomProxyConfig::New();
   auto delegate = CreateDelegate(std::move(config));
 
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
   net::ProxyInfo result;
   result.UseDirect();
   delegate->OnResolveProxy(GURL(kLocalhost),
@@ -966,129 +519,13 @@
   EXPECT_FALSE(result.is_for_ip_protection());
 }
 
-// When a `config` does look like an IP Protection `CustomProxyConfig`, but the
-// URLs do not match the allow list, the result is direct and not flagged as for
-// IP protection.
-TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyIpProtectionNoMatch) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  std::map<std::string, std::set<std::string>> first_party_map;
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"ippro-1"}, {"ippro-2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kLocalhost),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("http://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-// When a `config` does look like an IP Protection `CustomProxyConfig` but the
-// URL is HTTP and single-proxy chains are used, the result is direct and not
-// flagged as for IP Protection.
-// TODO(https://crbug.com/1474932): Once IP Protection chains are guaranteed to
-// be multi-proxy, we can remove this test.
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyIpProtectionSingleProxyHttpFailure) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"proxy"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("http://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-  EXPECT_TRUE(result.is_direct());
-  EXPECT_FALSE(result.is_for_ip_protection());
-}
-
-// When a `config` does look like an IP Protection `CustomProxyConfig` and the
-// URL is HTTP and multi-proxy chains are used, the result is flagged as for IP
-// protection and is not direct.
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyIpProtectionMultiProxyHttpSuccess) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"proxy1", "proxy2"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("http://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-  EXPECT_FALSE(result.is_direct());
-  EXPECT_TRUE(result.is_for_ip_protection());
-}
-
-// When a `config` does look like an IP Protection `CustomProxyConfig` and the
-// URLs match the allow list, and a token is available, the result is flagged as
-// for IP protection and is not direct.
-TEST_F(NetworkServiceProxyDelegateTest,
-       OnResolveProxyIpProtectionHttpsSuccess) {
-  auto config =
-      NetworkServiceProxyAllowList::MakeIpProtectionCustomProxyConfig();
-  std::map<std::string, std::set<std::string>> first_party_map;
-  first_party_map["example.com"] = {};
-  auto network_service_proxy_allow_list =
-      NetworkServiceProxyAllowList::CreateForTesting(first_party_map);
-  auto delegate =
-      CreateDelegate(std::move(config), &network_service_proxy_allow_list);
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetNextAuthToken(MakeAuthToken("Bearer: a-token"));
-  ipp_config_cache->SetProxyList({{"proxy"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  net::ProxyInfo result;
-  result.UseDirect();
-  delegate->OnResolveProxy(GURL(kHttpsUrl),
-                           net::NetworkAnonymizationKey::CreateCrossSite(
-                               net::SchemefulSite(GURL("https://top.com"))),
-                           "GET", net::ProxyRetryInfoMap(), &result);
-  EXPECT_FALSE(result.is_direct());
-  EXPECT_TRUE(result.is_for_ip_protection());
-}
-
 TEST_F(NetworkServiceProxyDelegateTest, InitialConfigUsedForProxy) {
   auto config = mojom::CustomProxyConfig::New();
   config->rules.ParseFromString("http=foo");
   mojo::Remote<mojom::CustomProxyConfigClient> client;
   auto delegate = std::make_unique<NetworkServiceProxyDelegate>(
       std::move(config), client.BindNewPipeAndPassReceiver(),
-      mojo::NullRemote(), nullptr);
+      mojo::NullRemote());
 
   net::ProxyInfo result;
   result.UseDirect();
@@ -1118,26 +555,6 @@
   EXPECT_EQ(TestObserver()->FallbackArgs()->second, net::ERR_FAILED);
 }
 
-TEST_F(NetworkServiceProxyDelegateTest, OnFallback_IpProtection) {
-  auto ip_protection_proxy_chain =
-      net::ProxyChain::FromSchemeHostAndPort(net::ProxyServer::SCHEME_HTTPS,
-                                             "proxy.com", absl::nullopt)
-          .ForIpProtection();
-  bool force_refresh_called = false;
-
-  auto config = mojom::CustomProxyConfig::New();
-  auto delegate = CreateDelegate(std::move(config));
-
-  auto ipp_config_cache = std::make_unique<MockIpProtectionConfigCache>();
-  ipp_config_cache->SetOnRequestRefreshProxyList(
-      base::BindLambdaForTesting([&]() { force_refresh_called = true; }));
-  ipp_config_cache->SetProxyList({{"proxy.com"}});
-  delegate->SetIpProtectionConfigCache(std::move(ipp_config_cache));
-
-  delegate->OnFallback(ip_protection_proxy_chain, net::ERR_FAILED);
-  EXPECT_TRUE(force_refresh_called);
-}
-
 TEST_F(NetworkServiceProxyDelegateTest, OnTunnelHeadersReceivedObserved) {
   net::ProxyChain proxy_chain({
       net::ProxyServer(net::ProxyServer::SCHEME_HTTP,
diff --git a/services/on_device_model/ml/chrome_ml.cc b/services/on_device_model/ml/chrome_ml.cc
index f3e0958..1175ee4 100644
--- a/services/on_device_model/ml/chrome_ml.cc
+++ b/services/on_device_model/ml/chrome_ml.cc
@@ -67,8 +67,8 @@
   kMaxValue = kDxgiErrorDeviceRemoved,
 };
 
-void FatalErrorFn(const char* msg) {
-  SCOPED_CRASH_KEY_STRING1024("ChromeML", "error_msg", msg);
+void FatalGpuErrorFn(const char* msg) {
+  SCOPED_CRASH_KEY_STRING1024("ChromeML(GPU)", "error_msg", msg);
   std::string msg_str(msg);
   GpuErrorReason error_reason = GpuErrorReason::kOther;
   if (msg_str.find("DXGI_ERROR_DEVICE_HUNG") != std::string::npos) {
@@ -79,12 +79,17 @@
   base::UmaHistogramEnumeration("OnDeviceModel.GpuErrorReason", error_reason);
   if (error_reason == GpuErrorReason::kOther) {
     // Collect crash reports on unknown errors.
-    CHECK(false) << "ChromeML Error: " << msg;
+    CHECK(false) << "ChromeML(GPU) Error: " << msg;
   } else {
     base::Process::TerminateCurrentProcessImmediately(0);
   }
 }
 
+void FatalErrorFn(const char* msg) {
+  SCOPED_CRASH_KEY_STRING1024("ChromeML", "error_msg", msg);
+  CHECK(false) << "ChromeML Error: " << msg;
+}
+
 }  // namespace
 
 ChromeML::ChromeML(base::PassKey<ChromeML>,
@@ -149,7 +154,10 @@
 
   api->InitDawnProcs(dawn::native::GetProcs());
   if (api->SetFatalErrorFn) {
-    api->SetFatalErrorFn(&FatalErrorFn);
+    api->SetFatalErrorFn(&FatalGpuErrorFn);
+  }
+  if (api->SetFatalErrorNonGpuFn) {
+    api->SetFatalErrorNonGpuFn(&FatalErrorFn);
   }
   return std::make_unique<ChromeML>(base::PassKey<ChromeML>(),
                                     std::move(scoped_library), api);
diff --git a/services/on_device_model/ml/chrome_ml_api.h b/services/on_device_model/ml/chrome_ml_api.h
index fd2004b..d52889c 100644
--- a/services/on_device_model/ml/chrome_ml_api.h
+++ b/services/on_device_model/ml/chrome_ml_api.h
@@ -211,7 +211,8 @@
   // functions.
   void (*InitDawnProcs)(const DawnProcTable& procs);
 
-  // Sets an error handling function for fatal errors.
+  // Sets an error handling function for fatal errors in the GPU. See also
+  // SetFatalErrorNonGpuFn.
   void (*SetFatalErrorFn)(ChromeMLFatalErrorFn error_fn) = nullptr;
 
   // Creates a new ChromeML model instance as described by `model`. The returned
@@ -238,6 +239,10 @@
   // Returns the GpuConfig in `config`. Returns true on success, false if there
   // was an error calculating it.
   bool (*GetGpuConfig)(GpuConfig& config);
+
+  // Same as SetFatalErrorFn(), but for fatal errors that occur outside of the
+  // gpu.
+  void (*SetFatalErrorNonGpuFn)(ChromeMLFatalErrorFn error_fn) = nullptr;
 };
 
 // Signature of the GetChromeMLAPI() function which the shared library exports.
diff --git a/sql/recovery.cc b/sql/recovery.cc
index 97f7318..4c3be723 100644
--- a/sql/recovery.cc
+++ b/sql/recovery.cc
@@ -94,6 +94,7 @@
           ? !BuiltInRecovery::ShouldAttemptRecovery(database, extended_error)
           : !database || !database->is_open() ||
                 database->DbPath(InternalApiToken()).empty() ||
+                database->UseWALMode() ||
                 !Recovery::ShouldRecover(extended_error)) {
     return false;
   }
diff --git a/sql/recovery.h b/sql/recovery.h
index e8a24250..93fedc7 100644
--- a/sql/recovery.h
+++ b/sql/recovery.h
@@ -22,8 +22,15 @@
 
 namespace sql {
 
-// Recovery module for sql/. Please see the `RecoverIfPossible()` method for
-// how to use this class.
+// Recovery module for sql/. Please see the `RecoverIfPossible()` method for how
+// to use this class.
+//
+// This module is capable of recovering databases which the legacy recovery
+// module could not recover. These include:
+//   - tables with the WITHOUT ROWID optimization
+//   - databases which use Write-Ahead Log (i.e. WAL mode)
+//     - NOTE: as WAL mode is still experimental (see https://crbug.com/1416213)
+//       recovery should not be attempted on WAL databases for now.
 //
 // Uses SQLite's recovery extension: https://www.sqlite.org/recovery.html
 class COMPONENT_EXPORT(SQL) BuiltInRecovery {
diff --git a/sql/recovery_unittest.cc b/sql/recovery_unittest.cc
index 47650956..5147ba2 100644
--- a/sql/recovery_unittest.cc
+++ b/sql/recovery_unittest.cc
@@ -93,6 +93,9 @@
   }
 
  protected:
+  explicit SqlRecoveryTestBase(DatabaseOptions options)
+      : db_(std::move(options)) {}
+
   base::test::ScopedFeatureList scoped_feature_list_;
   base::ScopedTempDir temp_dir_;
   base::FilePath db_path_;
@@ -102,15 +105,29 @@
 
 // Tests both the legacy `sql::Recovery` interface and the newer
 // `sql::BuiltInRecovery` interface, if it's supported.
-class SqlRecoveryTest : public SqlRecoveryTestBase,
-                        public testing::WithParamInterface<bool> {
+//
+// Parameters are as follows:
+//   - Is BuiltInRecovery enabled?
+//   - Is WAL mode enabled?
+class SqlRecoveryTest
+    : public SqlRecoveryTestBase,
+      public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
-  SqlRecoveryTest() {
-    scoped_feature_list_.InitWithFeatureState(
-        features::kUseBuiltInRecoveryIfSupported, GetParam());
+  SqlRecoveryTest()
+      : SqlRecoveryTestBase(DatabaseOptions{.wal_mode = UseWalMode()}) {
+    scoped_feature_list_.InitWithFeatureStates(
+        {{features::kUseBuiltInRecoveryIfSupported, std::get<0>(GetParam())},
+         {features::kEnableWALModeByDefault, UseWalMode()}});
   }
 
-  bool UseBuiltIn() { return GetParam() && BuiltInRecovery::IsSupported(); }
+  bool UseBuiltIn() {
+    return std::get<0>(GetParam()) && BuiltInRecovery::IsSupported();
+  }
+
+  bool UseWalMode() {
+    // The legacy recovery module does not support recovering WAL databases.
+    return std::get<1>(GetParam()) && BuiltInRecovery::IsSupported();
+  }
 };
 
 // Tests specific to the newer `sql::BuiltInRecovery` interface.
@@ -120,7 +137,7 @@
 // the legacy `sql::Recovery` module.
 class SqlBuiltInRecoveryTest : public SqlRecoveryTestBase {
  public:
-  SqlBuiltInRecoveryTest() {
+  SqlBuiltInRecoveryTest() : SqlRecoveryTestBase(DatabaseOptions{}) {
     scoped_feature_list_.InitAndEnableFeature(
         features::kUseBuiltInRecoveryIfSupported);
   }
@@ -133,9 +150,17 @@
 // the new `sql::BuiltInRecovery` module.
 class SqlLegacyRecoveryTest : public SqlRecoveryTestBase {
  public:
-  SqlLegacyRecoveryTest() {
+  SqlLegacyRecoveryTest()
+      // The legacy recovery module does not support recovering WAL databases.
+      : SqlRecoveryTestBase(DatabaseOptions{.wal_mode = false}) {
     scoped_feature_list_.InitAndDisableFeature(
         features::kUseBuiltInRecoveryIfSupported);
+
+    // TODO(https://crbug.com/1385500): All databases which use legacy recovery
+    // must either disable WAL mode manually or be migrated to the new recovery
+    // module before WAL mode may be turned on globally. This assertion is added
+    // here as a guard against accidental regression.
+    assert(!base::FeatureList::IsEnabled(features::kEnableWALModeByDefault));
   }
 };
 
@@ -1328,15 +1353,15 @@
   ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
   ASSERT_TRUE(Reopen());
 
-  // Run recovery code, then rollback.  Database remains the same.
-  {
+  if (!UseBuiltIn()) {
+    // Run recovery code, then rollback.  Database remains the same.
     std::unique_ptr<Recovery> recovery =
         Recovery::BeginRecoverDatabase(&db_, db_path_);
     ASSERT_TRUE(recovery);
     Recovery::Rollback(std::move(recovery));
+    db_.Close();
+    ASSERT_TRUE(Reopen());
   }
-  db_.Close();
-  ASSERT_TRUE(Reopen());
 
   static const char kIndexedCountSql[] =
       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
@@ -1547,9 +1572,56 @@
       IsSqliteSuccessCode(BuiltInRecovery::RecoverDatabase(
           &db_, BuiltInRecovery::Strategy::kRecoverOrRaze)));
 }
+
+// This test mimics the case where a database that was using WAL mode crashed,
+// then next Chrome launch the database is not opened in WAL mode. This may
+// occur when e.g. WAL mode if configured via Finch and the user not in the
+// experiment group on the second launch of Chrome.
+TEST_F(SqlBuiltInRecoveryTest, PRE_RecoverFormerlyWalDbAfterCrash) {
+  base::FilePath wal_db_path =
+      temp_dir_.GetPath().AppendASCII("recovery_wal_test.sqlite");
+
+  // Open the DB in WAL mode to set journal_mode="wal".
+  Database wal_db{{.wal_mode = true}};
+  ASSERT_TRUE(wal_db.Open(wal_db_path));
+
+  EXPECT_TRUE(wal_db.UseWALMode());
+  EXPECT_EQ(ExecuteWithResult(&wal_db, "PRAGMA journal_mode"), "wal");
+
+  // Crash the database somehow, foregoing the opportunity for any cleanup.
+  wal_db.set_error_callback(base::DoNothing());
+  EXPECT_DCHECK_DEATH(wal_db.set_error_callback(base::DoNothing()));
+}
+
+TEST_F(SqlBuiltInRecoveryTest, RecoverFormerlyWalDbAfterCrash) {
+  base::FilePath wal_db_path =
+      temp_dir_.GetPath().AppendASCII("recovery_wal_test.sqlite");
+
+  Database non_wal_db{{.wal_mode = false}};
+  ASSERT_TRUE(non_wal_db.Open(wal_db_path));
+
+  auto run_recovery = base::BindLambdaForTesting([&]() {
+    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                  &non_wal_db,
+                  BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
+              SqliteResultCode::kOk);
+  });
+
+  TestRecoverDatabase(non_wal_db, wal_db_path, /*with_meta=*/true,
+                      std::move(run_recovery));
+}
+
 #endif  // !BUILDFLAG(IS_FUCHSIA)
 
-INSTANTIATE_TEST_SUITE_P(All, SqlRecoveryTest, testing::Bool());
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    SqlRecoveryTest,
+    testing::Values(
+        std::tuple(true, true),   // BuiltInRecovery with WAL databases.
+        std::tuple(true, false),  // BuiltInRecovery with non-WAL databases.
+        std::tuple(false, false)  // Legacy recovery with non-WAL databases.
+        // The legacy recovery module does not support recovering WAL databases.
+        ));
 
 }  // namespace
 
diff --git a/testing/buildbot/filters/ios.use_blink.components_unittests.filter b/testing/buildbot/filters/ios.use_blink.components_unittests.filter
index 812d693f..1865832d 100644
--- a/testing/buildbot/filters/ios.use_blink.components_unittests.filter
+++ b/testing/buildbot/filters/ios.use_blink.components_unittests.filter
@@ -1,9 +1,11 @@
 # TODO(crbug.com/1427365): remove lines as more tests are supported on the platform.
 
-# TODO(crbug.com/1505584): currently fail with use_blink.
+# TODO(crbug.com/1505584): currently fail with use_blink due to spinning up on
+# an unexpected thread.
 -AutofillAcrossIframesTest.FeatureDisabled
 -AutofillAcrossIframesTest.NoChildFrames
 -AutofillAcrossIframesTest.WithChildFrames
+-AutofillAcrossIframesTest.Resolve
 
 # TODO(crbug.com/1505321): Test currently fails on iOS blink.
 -AutofillAgentTests.OnFormDataFilledTestWithFrameMessagingUsingRendererIDs
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 2bbc9e1..464fba67 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3425,6 +3425,25 @@
             ]
         }
     ],
+    "ChromeCompose": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "AutofillContentEditables",
+                        "Compose",
+                        "ComposeNudge"
+                    ]
+                }
+            ]
+        }
+    ],
     "ChromeLabs": [
         {
             "platforms": [
@@ -12294,22 +12313,6 @@
             ]
         }
     ],
-    "OnBeginFrameThrottleVideo": [
-        {
-            "platforms": [
-                "android",
-                "android_webview"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled_08072023",
-                    "enable_features": [
-                        "OnBeginFrameThrottleVideo"
-                    ]
-                }
-            ]
-        }
-    ],
     "OneCopyLegacyMPVideoFrameUploadViaSI": [
         {
             "platforms": [
@@ -14509,6 +14512,7 @@
                 {
                     "name": "Enabled",
                     "enable_features": [
+                        "FledgeDelayPostAuctionInterestGroupUpdate",
                         "FledgeUseInterestGroupCache"
                     ]
                 }
diff --git a/third_party/angle b/third_party/angle
index 92bd009..2937ee2 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 92bd0099204c0b5e8c6daecff9c691b895b87433
+Subproject commit 2937ee276cf5bd27fd5254091abdcf1e68a4155b
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 2915b16..8813d13 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -314,6 +314,23 @@
 const base::FeatureParam<int> kBoostImagePriorityTightMediumLimit{
     &kBoostImagePriority, "tight_medium_limit", 2};
 
+// Boost the priority of certain loading tasks (https://crbug.com/1470003).
+BASE_FEATURE(kBoostImageSetLoadingTaskPriority,
+             "BoostImageSetLoadingTaskPriority",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kBoostFontLoadingTaskPriority,
+             "BoostFontLoadingTaskPriority",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kBoostVideoLoadingTaskPriority,
+             "BoostVideoLoadingTaskPriority",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kBoostRenderBlockingStyleLoadingTaskPriority,
+             "BoostRenderBlockingStyleLoadingTaskPriority",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+BASE_FEATURE(kBoostNonRenderBlockingStyleLoadingTaskPriority,
+             "BoostNonRenderBlockingStyleLoadingTaskPriority",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // https://github.com/patcg-individual-drafts/topics
 // Kill switch for the Topics API.
 BASE_FEATURE(kBrowsingTopics,
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index db5c80aa..3843fe53 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -186,6 +186,15 @@
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kBoostImagePriorityTightMediumLimit;
 
+// Boost the priority of certain loading tasks (https://crbug.com/1470003).
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBoostImageSetLoadingTaskPriority);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBoostFontLoadingTaskPriority);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBoostVideoLoadingTaskPriority);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
+    kBoostRenderBlockingStyleLoadingTaskPriority);
+BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
+    kBoostNonRenderBlockingStyleLoadingTaskPriority);
+
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kBrowsingTopics);
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
     kBrowsingTopicsBypassIPIsPubliclyRoutableCheck);
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 4a347a9..6304eaf 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -399,6 +399,11 @@
   UpdateTitle(mojo_base.mojom.String16? title,
               mojo_base.mojom.TextDirection title_direction);
 
+  // Updates the app title for a document. If non-empty, a web app displayed in an app
+  // window may use this string instead of the regular title.
+  // See https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/DocumentSubtitle/explainer.md
+  UpdateAppTitle(mojo_base.mojom.String16 app_title);
+
   // Indicates that the user activation state in the current frame has been
   // updated, so the replicated states need to be synced (in the browser process
   // as well as in all other renderer processes).
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index 7d91ca0..fc43276 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -33,6 +33,7 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <tuple>
 #include <vector>
 
@@ -223,9 +224,7 @@
 
   // Returns the hash for the given canonicalized URL for use in visited
   // link coloring.
-  virtual uint64_t VisitedLinkHash(const char* canonical_url, size_t length) {
-    return 0;
-  }
+  virtual uint64_t VisitedLinkHash(std::string_view canonical_url) { return 0; }
 
   // Returns whether the given link hash is in the user's history. The
   // hash must have been generated by calling VisitedLinkHash().
diff --git a/third_party/blink/public/platform/task_type.h b/third_party/blink/public/platform/task_type.h
index 9b303c8..ccd3a060 100644
--- a/third_party/blink/public/platform/task_type.h
+++ b/third_party/blink/public/platform/task_type.h
@@ -53,9 +53,11 @@
   // This is a part of Networking task that should not be frozen when a page is
   // frozen.
   kNetworkingUnfreezable = 75,
-  // Tasks associated with image loading. Split off from kNetworkingUnfreezable
-  // so image loading tasks can be prioritized.
-  kNetworkingUnfreezableImageLoading = 83,
+  // Tasks associated with loading that should also block rendering. Split off
+  // from kNetworkingUnfreezable so tasks such as image loading can be
+  // prioritized above rendering. Note that not all render-blocking resources
+  // use this queue (e.g., script load tasks are not put here).
+  kNetworkingUnfreezableRenderBlockingLoading = 83,
   // This task source is used for control messages between kNetworking tasks.
   kNetworkingControl = 4,
   // Tasks used to run low priority scripts.
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index d9349048b..a979a463 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1498,6 +1498,7 @@
   sources +=
       rebase_path(blink_core_tests_execution_context, "", "execution_context")
   sources += rebase_path(blink_core_tests_fetch, "", "fetch")
+  sources += rebase_path(blink_core_tests_fileapi, "", "fileapi")
   sources += rebase_path(blink_core_tests_frame, "", "frame")
   sources += rebase_path(blink_core_tests_fullscreen, "", "fullscreen")
   sources += rebase_path(blink_core_tests_geometry, "", "geometry")
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index de3db04..3bf7f53 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -7747,6 +7747,25 @@
   return absl::nullopt;
 }
 
+void Document::UpdateAppTitle() {
+  auto* root_element = documentElement();
+  if (!root_element) {
+    return;
+  }
+
+  for (HTMLMetaElement& meta_element :
+       Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) {
+    if (EqualIgnoringASCIICase(meta_element.GetName(), "app-title")) {
+      GetFrame()->GetLocalFrameHostRemote().UpdateAppTitle(
+          meta_element.Content().GetString());
+      return;
+    }
+  }
+
+  // Handle case of meta tag being removed by setting app title to empty string.
+  GetFrame()->GetLocalFrameHostRemote().UpdateAppTitle(String(""));
+}
+
 void Document::ColorSchemeMetaChanged() {
   const CSSValue* color_scheme = nullptr;
   if (auto* root_element = documentElement()) {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 13f49dd9..89f7857 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -2016,6 +2016,9 @@
   // it.
   bool HasAtLeastOneDataList() const { return data_list_count_; }
 
+  // Updates app title based to the latest app title meta tag value.
+  void UpdateAppTitle();
+
   void ResetAgent(Agent& agent);
 
   bool SupportsLegacyDOMMutations();
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
index 7b3a50a0..61df215 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller.cc
@@ -58,6 +58,7 @@
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
+#include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
 #include "third_party/blink/renderer/core/input/event_handler.h"
@@ -1826,6 +1827,16 @@
 }
 
 WebTextInputType InputMethodController::TextInputType() const {
+  // Since selection can never go inside a <canvas> element, if the user is
+  // editing inside a <canvas> with EditContext we need to handle that case
+  // directly before looking at the selection position.
+  if (GetActiveEditContext()) {
+    Element* element = GetDocument().FocusedElement();
+    if (IsA<HTMLCanvasElement>(*element)) {
+      return kWebTextInputTypeContentEditable;
+    }
+  }
+
   if (!GetFrame().Selection().IsAvailable()) {
     // "mouse-capture-inside-shadow.html" reaches here.
     return kWebTextInputTypeNone;
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
index 40777759..c6371e0c 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
@@ -3719,4 +3719,23 @@
             GetSelectionTextFromBody());
 }
 
+TEST_F(InputMethodControllerTest, EditContextCanvasHasEditableType) {
+  GetDocument().GetSettings()->SetScriptEnabled(true);
+  Element* noneditable_canvas = InsertHTMLElement(
+      "<canvas id='noneditable-canvas'></canvas>", "noneditable-canvas");
+  Element* editable_canvas = InsertHTMLElement(
+      "<canvas id='editable-canvas'></canvas>", "editable-canvas");
+  Element* script = GetDocument().CreateRawElement(html_names::kScriptTag);
+  script->setInnerHTML(
+      "document.getElementById('editable-canvas').editContext = new "
+      "EditContext()");
+  GetDocument().body()->AppendChild(script);
+  UpdateAllLifecyclePhasesForTest();
+
+  noneditable_canvas->Focus();
+  EXPECT_EQ(kWebTextInputTypeNone, Controller().TextInputType());
+
+  editable_canvas->Focus();
+  EXPECT_EQ(kWebTextInputTypeContentEditable, Controller().TextInputType());
+}
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/fileapi/file_list_test.cc b/third_party/blink/renderer/core/fileapi/file_list_test.cc
index 45082d13..d3019cf 100644
--- a/third_party/blink/renderer/core/fileapi/file_list_test.cc
+++ b/third_party/blink/renderer/core/fileapi/file_list_test.cc
@@ -10,10 +10,12 @@
 #include "third_party/blink/renderer/core/testing/null_execution_context.h"
 #include "third_party/blink/renderer/platform/blob/blob_data.h"
 #include "third_party/blink/renderer/platform/file_metadata.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 
 namespace blink {
 
 TEST(FileListTest, pathsForUserVisibleFiles) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   auto* const file_list = MakeGarbageCollected<FileList>();
 
diff --git a/third_party/blink/renderer/core/fileapi/file_test.cc b/third_party/blink/renderer/core/fileapi/file_test.cc
index e0ccab8b..f9a5f34 100644
--- a/third_party/blink/renderer/core/fileapi/file_test.cc
+++ b/third_party/blink/renderer/core/fileapi/file_test.cc
@@ -24,6 +24,7 @@
 #include "third_party/blink/renderer/platform/file_metadata.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_base.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_copier_mojo.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
@@ -176,6 +177,7 @@
 }  // namespace
 
 TEST(FileTest, NativeFileWithoutTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   auto* const file = MakeGarbageCollected<File>(&context.GetExecutionContext(),
                                                 "/native/path");
@@ -188,6 +190,7 @@
 }
 
 TEST(FileTest, NativeFileWithUnixEpochTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   auto* const file = MakeGarbageCollected<File>(&context.GetExecutionContext(),
                                                 "/native/path");
@@ -199,6 +202,7 @@
 }
 
 TEST(FileTest, NativeFileWithApocalypseTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   auto* const file = MakeGarbageCollected<File>(&context.GetExecutionContext(),
                                                 "/native/path");
@@ -212,6 +216,7 @@
 }
 
 TEST(FileTest, BlobBackingFileWithoutTimestamp) {
+  test::TaskEnvironment task_environment;
   auto* const file = MakeGarbageCollected<File>("name", absl::nullopt,
                                                 BlobDataHandle::Create());
   EXPECT_FALSE(file->HasBackingFile());
@@ -221,6 +226,7 @@
 }
 
 TEST(FileTest, BlobBackingFileWithWindowsEpochTimestamp) {
+  test::TaskEnvironment task_environment;
   auto* const file = MakeGarbageCollected<File>("name", base::Time(),
                                                 BlobDataHandle::Create());
   EXPECT_FALSE(file->HasBackingFile());
@@ -232,6 +238,7 @@
 }
 
 TEST(FileTest, BlobBackingFileWithUnixEpochTimestamp) {
+  test::TaskEnvironment task_environment;
   const scoped_refptr<BlobDataHandle> blob_data_handle =
       BlobDataHandle::Create();
   auto* const file = MakeGarbageCollected<File>("name", base::Time::UnixEpoch(),
@@ -244,6 +251,7 @@
 }
 
 TEST(FileTest, BlobBackingFileWithApocalypseTimestamp) {
+  test::TaskEnvironment task_environment;
   constexpr base::Time kMaxTime = base::Time::Max();
   auto* const file =
       MakeGarbageCollected<File>("name", kMaxTime, BlobDataHandle::Create());
@@ -256,6 +264,7 @@
 }
 
 TEST(FileTest, fileSystemFileWithNativeSnapshot) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   FileMetadata metadata;
   metadata.platform_path = "/native/snapshot";
@@ -267,6 +276,7 @@
 }
 
 TEST(FileTest, fileSystemFileWithNativeSnapshotAndSize) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   FileMetadata metadata;
   metadata.length = 1024ll;
@@ -279,6 +289,7 @@
 }
 
 TEST(FileTest, FileSystemFileWithWindowsEpochTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   FileMetadata metadata;
   metadata.length = INT64_C(1025);
@@ -296,6 +307,7 @@
 }
 
 TEST(FileTest, FileSystemFileWithUnixEpochTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   FileMetadata metadata;
   metadata.length = INT64_C(1025);
@@ -312,6 +324,7 @@
 }
 
 TEST(FileTest, FileSystemFileWithApocalypseTimestamp) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   constexpr base::Time kMaxTime = base::Time::Max();
   FileMetadata metadata;
@@ -330,6 +343,7 @@
 }
 
 TEST(FileTest, fileSystemFileWithoutNativeSnapshot) {
+  test::TaskEnvironment task_environment;
   KURL url("filesystem:http://example.com/isolated/hash/non-native-file");
   FileMetadata metadata;
   metadata.length = 0;
@@ -341,6 +355,7 @@
 }
 
 TEST(FileTest, hsaSameSource) {
+  test::TaskEnvironment task_environment;
   ScopedNullExecutionContext context;
   auto* const native_file_a1 = MakeGarbageCollected<File>(
       &context.GetExecutionContext(), "/native/pathA");
@@ -388,6 +403,7 @@
 }
 
 TEST(FileTest, createForFileSystem) {
+  test::TaskEnvironment task_environment;
   V8TestingScope scope(KURL("http://example.com"));
   Document& document = scope.GetDocument();
   base::RunLoop run_loop;
diff --git a/third_party/blink/renderer/core/fileapi/public_url_manager_test.cc b/third_party/blink/renderer/core/fileapi/public_url_manager_test.cc
index 6e34ccd..50f7bb2 100644
--- a/third_party/blink/renderer/core/fileapi/public_url_manager_test.cc
+++ b/third_party/blink/renderer/core/fileapi/public_url_manager_test.cc
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/platform/blob/testing/fake_blob_url_store.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 
 namespace blink {
@@ -125,6 +126,8 @@
   }
 
  protected:
+  test::TaskEnvironment task_environment_;
+
   PublicURLManagerTestCase test_case_;
   base::test::ScopedFeatureList scoped_feature_list_;
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 8cb1426..b6d0db6 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -2005,8 +2005,6 @@
   if (!resize_controller)
     return false;
 
-  DCHECK(Lifecycle().GetState() >= DocumentLifecycle::kPrePaintClean);
-
   size_t min_depth = resize_controller->GatherObservations();
 
   if (min_depth != ResizeObserverController::kDepthBottom) {
diff --git a/third_party/blink/renderer/core/html/custom/element_internals.cc b/third_party/blink/renderer/core/html/custom/element_internals.cc
index 78a37b1..56e9124a 100644
--- a/third_party/blink/renderer/core/html/custom/element_internals.cc
+++ b/third_party/blink/renderer/core/html/custom/element_internals.cc
@@ -334,6 +334,10 @@
 
 void ElementInternals::SetElementAttribute(const QualifiedName& name,
                                            Element* element) {
+  if (!element) {
+    explicitly_set_attr_elements_map_.erase(name);
+    return;
+  }
   auto result = explicitly_set_attr_elements_map_.insert(name, nullptr);
   if (result.is_new_entry) {
     result.stored_value->value =
diff --git a/third_party/blink/renderer/core/html/html_meta_element.cc b/third_party/blink/renderer/core/html/html_meta_element.cc
index 73c80be..53c6e6c 100644
--- a/third_party/blink/renderer/core/html/html_meta_element.cc
+++ b/third_party/blink/renderer/core/html/html_meta_element.cc
@@ -547,6 +547,9 @@
              EqualIgnoringASCIICase(name_value, "view-transition")) {
     ViewTransitionSupplement::From(GetDocument())
         ->OnMetaTagChanged(g_null_atom);
+  } else if (RuntimeEnabledFeatures::AppTitleEnabled() &&
+             EqualIgnoringASCIICase(name_value, "app-title")) {
+    GetDocument().UpdateAppTitle();
   }
 }
 
@@ -724,6 +727,9 @@
              EqualIgnoringASCIICase(name_value, "view-transition")) {
     ViewTransitionSupplement::From(GetDocument())
         ->OnMetaTagChanged(content_value);
+  } else if (RuntimeEnabledFeatures::AppTitleEnabled() &&
+             EqualIgnoringASCIICase(name_value, "app-title")) {
+    GetDocument().UpdateAppTitle();
   }
 }
 
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
index 48944ec9..974a3057 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
@@ -112,6 +112,8 @@
     const WTF::String& title,
     base::i18n::TextDirection title_direction) {}
 
+void FakeLocalFrameHost::UpdateAppTitle(const WTF::String& app_title) {}
+
 void FakeLocalFrameHost::UpdateUserActivationState(
     mojom::blink::UserActivationUpdateType update_type,
     mojom::UserActivationNotificationType notification_type) {}
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.h b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
index 6334fe6..d4ef3eb 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
@@ -84,6 +84,7 @@
   void NavigateEventHandlerPresenceChanged(bool present) override {}
   void UpdateTitle(const WTF::String& title,
                    base::i18n::TextDirection title_direction) override;
+  void UpdateAppTitle(const WTF::String& app_title) override;
   void UpdateUserActivationState(
       mojom::blink::UserActivationUpdateType update_type,
       mojom::UserActivationNotificationType notification_type) override;
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
index 092aae3..9f125a44 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
@@ -120,11 +120,11 @@
 
 void SoftNavigationHeuristics::ResetHeuristic() {
   // Reset previously seen indicators and task IDs.
-  potential_soft_navigation_task_ids_.clear();
+  has_potential_soft_navigation_task_ = false;
+  potential_soft_navigation_tasks_.clear();
   interaction_task_id_to_interaction_data_.clear();
   last_interaction_task_id_ = 0;
   last_soft_navigation_ancestor_task_ = absl::nullopt;
-  disposed_soft_navigation_tasks_ = 0;
   soft_navigation_descendant_cache_.clear();
   SetIsTrackingSoftNavigationHeuristicsOnDocument(false);
   did_reset_paints_ = false;
@@ -191,7 +191,7 @@
     ScriptState* script_state) {
   using IterationStatus = scheduler::TaskAttributionTracker::IterationStatus;
 
-  if (potential_soft_navigation_task_ids_.empty()) {
+  if (potential_soft_navigation_tasks_.empty()) {
     return absl::nullopt;
   }
   ThreadScheduler* scheduler = ThreadScheduler::Current();
@@ -208,12 +208,11 @@
       return cached_result->value;
     }
     absl::optional<scheduler::TaskAttributionId> ancestor_task_id;
-    // Check if any of `potential_soft_navigation_task_ids_` is an ancestor of
+    // Check if any of `potential_soft_navigation_tasks_` is an ancestor of
     // `task`.
     tracker->ForEachAncestor(
         *task, [&](const scheduler::TaskAttributionInfo& ancestor) {
-          if (potential_soft_navigation_task_ids_.Contains(
-                  ancestor.Id().value())) {
+          if (potential_soft_navigation_tasks_.Contains(&ancestor)) {
             ancestor_task_id = ancestor.Id();
             return IterationStatus::kStop;
           }
@@ -498,6 +497,13 @@
 
 void SoftNavigationHeuristics::Trace(Visitor* visitor) const {
   Supplement<LocalDOMWindow>::Trace(visitor);
+  visitor->Trace(potential_soft_navigation_tasks_);
+  // Register a custom weak callback, which runs after processing weakness for
+  // the container. This allows us to observe the collection becoming empty
+  // without needing to observe individual element disposal.
+  visitor->RegisterWeakCallbackMethod<
+      SoftNavigationHeuristics,
+      &SoftNavigationHeuristics::ProcessCustomWeakness>(this);
 }
 
 void SoftNavigationHeuristics::OnCreateTaskScope(
@@ -510,12 +516,12 @@
   if (!tracker) {
     return;
   }
-  tracker->SetObserverForTaskDisposal(task.Id(), this);
   // We're inside a click event handler, so need to add this task to the set of
   // potential soft navigation root tasks.
   TRACE_EVENT1("scheduler", "SoftNavigationHeuristics::OnCreateTaskScope",
                "task_id", task.Id().value());
-  potential_soft_navigation_task_ids_.insert(task.Id().value());
+  potential_soft_navigation_tasks_.insert(&task);
+  has_potential_soft_navigation_task_ = true;
   if (!pending_interaction_timestamp_.is_null()) {
     PerInteractionData data;
     data.user_interaction_timestamp = pending_interaction_timestamp_;
@@ -534,17 +540,18 @@
   }
 }
 
-void SoftNavigationHeuristics::OnTaskDisposal(
-    const scheduler::TaskAttributionInfo& task) {
-  if (potential_soft_navigation_task_ids_.Contains(task.Id().value())) {
-    if (++disposed_soft_navigation_tasks_ >=
-        potential_soft_navigation_task_ids_.size()) {
-      // When all the soft navigation tasks were garbage collected, that means
-      // that all their descendant tasks are done, and there's no need to
-      // continue searching for soft navigation signals, at least not until the
-      // next user interaction.
-      ResetHeuristic();
-    }
+void SoftNavigationHeuristics::ProcessCustomWeakness(
+    const LivenessBroker& info) {
+  // When all the soft navigation tasks were garbage collected, that means that
+  // all their descendant tasks are done, and there's no need to continue
+  // searching for soft navigation signals, at least not until the next user
+  // interaction.
+  //
+  // Note: This is not allowed to do Oilpan allocations. If that's needed, this
+  // can schedule a task or microtask to reset the heuristic.
+  if (has_potential_soft_navigation_task_ &&
+      potential_soft_navigation_tasks_.empty()) {
+    ResetHeuristic();
   }
 }
 
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
index a1cdeab..1d204ecb 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
@@ -9,6 +9,7 @@
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
@@ -73,7 +74,6 @@
   // TaskAttributionTracker::Observer's implementation.
   void OnCreateTaskScope(scheduler::TaskAttributionInfo&,
                          ScriptState*) override;
-  void OnTaskDisposal(const scheduler::TaskAttributionInfo&) override;
   ExecutionContext* GetExecutionContext() override;
 
   void RecordPaint(LocalFrame*,
@@ -87,10 +87,19 @@
   // current_event_parameters_ and return true. Otherwise, return false.
   bool PopNestedEventParametersIfNeeded();
 
+  // This method is called during the weakness processing stage of garbage
+  // collection, and it's used to detect `potential_soft_navigation_tasks_`
+  // becoming empty.
+  void ProcessCustomWeakness(const LivenessBroker& info);
+
   bool GetInitialInteractionEncounteredForTest() {
     return initial_interaction_encountered_;
   }
 
+  scheduler::TaskAttributionIdType GetLastInteractionTaskIdForTest() const {
+    return last_interaction_task_id_;
+  }
+
  private:
   enum FlagType : uint8_t {
     kURLChange,
@@ -124,9 +133,8 @@
 
   PerInteractionData* GetCurrentInteractionData(scheduler::TaskAttributionId);
 
-  WTF::HashSet<scheduler::TaskAttributionIdType>
-      potential_soft_navigation_task_ids_;
-  size_t disposed_soft_navigation_tasks_ = 0;
+  HeapHashSet<WeakMember<const scheduler::TaskAttributionInfo>>
+      potential_soft_navigation_tasks_;
   WTF::HashMap<scheduler::TaskAttributionIdType,
                absl::optional<scheduler::TaskAttributionId>>
       soft_navigation_descendant_cache_;
@@ -159,12 +167,15 @@
   EventParameters top_event_parameters_;
   WTF::Deque<EventParameters> nested_event_parameters_;
   EventParameters* current_event_parameters_ = nullptr;
+  // Used to synchronize resetting the heuristic when
+  // `potential_soft_navigation_tasks_` becomes empty during GC.
+  bool has_potential_soft_navigation_task_ = false;
 };
 
 // This class defines a scope that would cover click or navigation related
 // events, in order for the SoftNavigationHeuristics class to be able to keep
 // track of them and their descendant tasks.
-class SoftNavigationEventScope {
+class CORE_EXPORT SoftNavigationEventScope {
  public:
   SoftNavigationEventScope(SoftNavigationHeuristics* heuristics,
                            SoftNavigationHeuristics::EventScopeType type,
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
index 64eaab78..691d682 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
@@ -10,9 +10,16 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/heap/thread_state.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
+#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 
 namespace blink {
 
+using TaskScope = scheduler::TaskAttributionTracker::TaskScope;
+using TaskScopeType = scheduler::TaskAttributionTracker::TaskScopeType;
+
 class SoftNavigationHeuristicsTest : public testing::Test {
  protected:
   void SetUp() override {
@@ -114,4 +121,47 @@
           kUserInteractionTsAndReferenceTsBothNotNull,
       1);
 }
+
+TEST_F(SoftNavigationHeuristicsTest, ResetHeuristicOnSetBecameEmpty) {
+  auto* heuristics = CreateSoftNavigationHeuristicsForTest();
+  ASSERT_TRUE(heuristics);
+  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+  ASSERT_TRUE(tracker);
+
+  auto* script_state = GetScriptStateForTest();
+  Persistent<scheduler::TaskAttributionInfo> root_task = nullptr;
+  // Simulate a click.
+  {
+    SoftNavigationEventScope event_scope(
+        heuristics, SoftNavigationHeuristics::EventScopeType::kClick,
+        /*is_new_interaction=*/true);
+    std::unique_ptr<TaskScope> task_scope = tracker->CreateTaskScope(
+        script_state, /*parent_task=*/nullptr, TaskScopeType::kCallback);
+    root_task = tracker->RunningTask(script_state);
+  }
+  EXPECT_TRUE(root_task);
+  EXPECT_GT(heuristics->GetLastInteractionTaskIdForTest(), 0u);
+
+  // Simulate a descendant task.
+  Persistent<scheduler::TaskAttributionInfo> descendant_task = nullptr;
+  {
+    std::unique_ptr<TaskScope> task_scope = tracker->CreateTaskScope(
+        script_state, root_task, TaskScopeType::kCallback);
+    descendant_task = tracker->RunningTask(script_state);
+  }
+  EXPECT_TRUE(descendant_task);
+
+  root_task = nullptr;
+  ThreadState::Current()->CollectAllGarbageForTesting();
+  // The heuristics still should not have been reset since there is a live
+  // root task, which is being held onto by its descendant task.
+  EXPECT_GT(heuristics->GetLastInteractionTaskIdForTest(), 0u);
+
+  // Finally, this should allow the click task to be GCed, which should cause
+  // the heuristics to be reset.
+  descendant_task = nullptr;
+  ThreadState::Current()->CollectAllGarbageForTesting();
+  EXPECT_EQ(heuristics->GetLastInteractionTaskIdForTest(), 0u);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
index a9b9a6fc..35192144 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
@@ -184,23 +184,6 @@
   return ScriptWrappableTaskState::GetCurrent(script_state);
 }
 
-TaskAttributionTracker::Observer*
-TaskAttributionTrackerImpl::GetObserverForTaskDisposal(TaskAttributionId id) {
-  auto it = task_id_observers_.find(id.value());
-  if (it == task_id_observers_.end()) {
-    return nullptr;
-  }
-  auto* observer = it->value.Get();
-  task_id_observers_.erase(it);
-  return observer;
-}
-
-void TaskAttributionTrackerImpl::SetObserverForTaskDisposal(
-    TaskAttributionId id,
-    Observer* observer) {
-  task_id_observers_.insert(id.value(), observer);
-}
-
 // TaskScope's implementation
 //////////////////////////////////////
 TaskAttributionTrackerImpl::TaskScopeImpl::TaskScopeImpl(
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
index db6be50..343d244 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
@@ -90,9 +90,6 @@
   virtual ScriptWrappableTaskState* GetCurrentTaskContinuationData(
       ScriptState*) const;
 
-  Observer* GetObserverForTaskDisposal(TaskAttributionId) override;
-  void SetObserverForTaskDisposal(TaskAttributionId, Observer*) override;
-
  private:
   struct TaskAttributionIdPair {
     TaskAttributionIdPair() = default;
@@ -149,9 +146,6 @@
   Persistent<TaskAttributionInfo> running_task_ = nullptr;
 
   WTF::HashSet<WeakPersistent<TaskAttributionTracker::Observer>> observers_;
-  WTF::HashMap<TaskAttributionIdType,
-               WeakPersistent<TaskAttributionTracker::Observer>>
-      task_id_observers_;
 
   // A queue of TaskAttributionInfo objects representing tasks that initiated a
   // same-document navigation that was sent to the browser side. They are kept
diff --git a/third_party/blink/renderer/modules/storage/cached_storage_area.cc b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
index 771cb77..cb7bf9e 100644
--- a/third_party/blink/renderer/modules/storage/cached_storage_area.cc
+++ b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
@@ -244,7 +244,7 @@
     remote_area_.Bind(std::move(new_area), task_runner);
   } else if (storage_namespace_) {
     storage_namespace_->BindStorageArea(
-        *local_dom_window,
+        storage_key_, local_dom_window->GetLocalFrameToken(),
         remote_area_.BindNewPipeAndPassReceiver(task_runner));
   } else {
     return;
diff --git a/third_party/blink/renderer/modules/storage/storage_namespace.cc b/third_party/blink/renderer/modules/storage/storage_namespace.cc
index 91696379..f7c3a7e 100644
--- a/third_party/blink/renderer/modules/storage/storage_namespace.cc
+++ b/third_party/blink/renderer/modules/storage/storage_namespace.cc
@@ -230,17 +230,15 @@
 }
 
 void StorageNamespace::BindStorageArea(
-    const LocalDOMWindow& local_dom_window,
+    const BlinkStorageKey& storage_key,
+    const LocalFrameToken& local_frame_token,
     mojo::PendingReceiver<mojom::blink::StorageArea> receiver) {
   if (IsSessionStorage()) {
     controller_->dom_storage()->BindSessionStorageArea(
-        local_dom_window.GetSessionStorageKey(),
-        local_dom_window.GetLocalFrameToken(), namespace_id_,
-        std::move(receiver));
+        storage_key, local_frame_token, namespace_id_, std::move(receiver));
   } else {
-    controller_->dom_storage()->OpenLocalStorage(
-        local_dom_window.GetStorageKey(), local_dom_window.GetLocalFrameToken(),
-        std::move(receiver));
+    controller_->dom_storage()->OpenLocalStorage(storage_key, local_frame_token,
+                                                 std::move(receiver));
   }
 }
 
diff --git a/third_party/blink/renderer/modules/storage/storage_namespace.h b/third_party/blink/renderer/modules/storage/storage_namespace.h
index f62b185..6ae87db 100644
--- a/third_party/blink/renderer/modules/storage/storage_namespace.h
+++ b/third_party/blink/renderer/modules/storage/storage_namespace.h
@@ -119,7 +119,8 @@
   // Called by areas in `cached_areas_` to bind/rebind their StorageArea
   // interface.
   void BindStorageArea(
-      const LocalDOMWindow& local_dom_window,
+      const BlinkStorageKey& storage_key,
+      const LocalFrameToken& local_frame_token,
       mojo::PendingReceiver<mojom::blink::StorageArea> receiver);
 
   // If this StorageNamespace was previously connected to the backend, this
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index bd01cf48..732e7e9 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -16,6 +16,7 @@
 import("//third_party/blink/renderer/build/scripts/scripts.gni")
 import("//third_party/blink/renderer/config.gni")
 import("//third_party/blink/renderer/platform/platform_generated.gni")
+import("//third_party/libgav1/options.gni")
 import("//v8/gni/v8.gni")
 
 if (is_ios) {
@@ -1310,6 +1311,8 @@
     "peerconnection/gpu_codec_support_waiter.h",
     "peerconnection/linear_histogram.cc",
     "peerconnection/linear_histogram.h",
+    "peerconnection/resolution_monitor.cc",
+    "peerconnection/resolution_monitor.h",
     "peerconnection/rtc_answer_options_platform.h",
     "peerconnection/rtc_api_name.h",
     "peerconnection/rtc_dtmf_sender_handler.cc",
@@ -1667,6 +1670,8 @@
     "//third_party/blink/renderer/platform/scheduler",
   ]
 
+  assert(use_libgav1_parser)
+
   public_deps = [
     ":allow_discouraged_type",
     ":blink_platform_public_deps",
@@ -1714,6 +1719,7 @@
     "//media/capture:capture_switches",
     "//media/capture/mojom:video_capture",
     "//media/mojo/clients:clients",
+    "//media/parsers",
     "//media/webrtc:webrtc",
     "//mojo/public/cpp/bindings:wtf_support",
     "//services/metrics/public/cpp:ukm_builders",
@@ -1732,6 +1738,7 @@
     "//third_party/ced",
     "//third_party/emoji-segmenter",
     "//third_party/icu",
+    "//third_party/libgav1:libgav1_parser",
     "//third_party/libyuv",
     "//third_party/one_euro_filter",
     "//third_party/snappy:snappy",
@@ -2174,6 +2181,7 @@
     "peerconnection/linear_histogram_test.cc",
     "peerconnection/low_precision_timer_test.cc",
     "peerconnection/metronome_source_test.cc",
+    "peerconnection/resolution_monitor_unittest.cc",
     "peerconnection/rtc_encoded_audio_stream_transformer_test.cc",
     "peerconnection/rtc_encoded_video_stream_transformer_test.cc",
     "peerconnection/rtc_rtp_source_test.cc",
diff --git a/third_party/blink/renderer/platform/link_hash.cc b/third_party/blink/renderer/platform/link_hash.cc
index 5c618f9..dc7fafa 100644
--- a/third_party/blink/renderer/platform/link_hash.cc
+++ b/third_party/blink/renderer/platform/link_hash.cc
@@ -30,10 +30,11 @@
 
 #include "third_party/blink/renderer/platform/link_hash.h"
 
+#include <string_view>
+
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
-
 #include "url/url_util.h"
 
 namespace blink {
@@ -62,7 +63,9 @@
   url::RawCanonOutput<2048> buffer;
   if (!ResolveRelative(base, relative.GetString(), &buffer))
     return 0;
-  return Platform::Current()->VisitedLinkHash(buffer.data(), buffer.length());
+
+  return Platform::Current()->VisitedLinkHash(
+      std::string_view(buffer.data(), buffer.length()));
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 77ef55e..16ba1cc 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -1347,6 +1347,37 @@
   resource->SetRevalidatingRequest(revalidating_request);
 }
 
+namespace {
+
+bool UseRenderBlockingTaskPriority(const ResourceRequestHead& request) {
+  switch (request.GetRequestContext()) {
+    case mojom::blink::RequestContextType::IMAGE:
+      // Always boost the priority of images (see: https://crbug.com/1416030).
+      return true;
+    case mojom::blink::RequestContextType::IMAGE_SET:
+      return base::FeatureList::IsEnabled(
+          features::kBoostImageSetLoadingTaskPriority);
+    case mojom::blink::RequestContextType::FONT:
+      return base::FeatureList::IsEnabled(
+          features::kBoostFontLoadingTaskPriority);
+    case mojom::blink::RequestContextType::VIDEO:
+      return base::FeatureList::IsEnabled(
+          features::kBoostVideoLoadingTaskPriority);
+    case mojom::blink::RequestContextType::STYLE:
+      if (request.GetRenderBlockingBehavior() ==
+          RenderBlockingBehavior::kBlocking) {
+        return base::FeatureList::IsEnabled(
+            features::kBoostRenderBlockingStyleLoadingTaskPriority);
+      }
+      return base::FeatureList::IsEnabled(
+          features::kBoostNonRenderBlockingStyleLoadingTaskPriority);
+    default:
+      return false;
+  }
+}
+
+}  // namespace
+
 std::unique_ptr<URLLoader> ResourceFetcher::CreateURLLoader(
     const ResourceRequestHead& request,
     const ResourceLoaderOptions& options) {
@@ -1376,13 +1407,12 @@
             frame_scheduler->GetAgentGroupScheduler()->DefaultTaskRunner();
       }
     }
-  } else if (request.GetRequestContext() ==
-             mojom::blink::RequestContextType::IMAGE) {
+  } else if (UseRenderBlockingTaskPriority(request)) {
     if (auto* frame_or_worker_scheduler = GetFrameOrWorkerScheduler()) {
       if (auto* frame_scheduler =
               frame_or_worker_scheduler->ToFrameScheduler()) {
         task_runner = frame_scheduler->GetTaskRunner(
-            TaskType::kNetworkingUnfreezableImageLoading);
+            TaskType::kNetworkingUnfreezableRenderBlockingLoading);
       }
     }
   }
diff --git a/third_party/blink/renderer/platform/peerconnection/DEPS b/third_party/blink/renderer/platform/peerconnection/DEPS
index d26b9a6e..8994d0de 100644
--- a/third_party/blink/renderer/platform/peerconnection/DEPS
+++ b/third_party/blink/renderer/platform/peerconnection/DEPS
@@ -19,8 +19,10 @@
     "+media/capture/video/video_capture_feedback.h",
     "+media/filters/decoder_stream.h",
     "+media/filters/resolution_monitor.h",
+    "+media/filters/vp9_parser.h",
     "+media/media_buildflags.h",
     "+media/mojo/clients/mojo_video_encoder_metrics_provider.h",
+    "+media/parsers/vp8_parser.h",
     # This is only needed for a cast to DecoderFactory.
     "+media/renderers/default_decoder_factory.h",
     "+media/video/gpu_video_accelerator_factories.h",
@@ -47,4 +49,7 @@
         "+media/mojo/clients/mojo_video_encoder_metrics_provider.h",
         "+third_party/blink/renderer/platform/testing/video_frame_utils.h",
     ],
+    "resolution_monitor_unittest.cc" : [
+        "+media/filters/ivf_parser.h",
+    ]
 }
diff --git a/media/filters/resolution_monitor.cc b/third_party/blink/renderer/platform/peerconnection/resolution_monitor.cc
similarity index 73%
rename from media/filters/resolution_monitor.cc
rename to third_party/blink/renderer/platform/peerconnection/resolution_monitor.cc
index c2afde4..5d6a649 100644
--- a/media/filters/resolution_monitor.cc
+++ b/third_party/blink/renderer/platform/peerconnection/resolution_monitor.cc
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/filters/resolution_monitor.h"
+#include "third_party/blink/renderer/platform/peerconnection/resolution_monitor.h"
 
+#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/notreached.h"
@@ -11,26 +12,28 @@
 #include "media/base/decoder_buffer.h"
 #include "media/filters/vp9_parser.h"
 #include "media/parsers/vp8_parser.h"
-#include "media/video/h264_parser.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/libgav1/src/src/buffer_pool.h"
 #include "third_party/libgav1/src/src/decoder_state.h"
 #include "third_party/libgav1/src/src/obu_parser.h"
+#include "third_party/webrtc/common_video/h264/h264_common.h"
+#include "third_party/webrtc/common_video/h264/sps_parser.h"
 
-namespace media {
+namespace blink {
+
 namespace {
 
 class Vp8ResolutionMonitor : public ResolutionMonitor {
  public:
   Vp8ResolutionMonitor() = default;
   absl::optional<gfx::Size> GetResolution(
-      const DecoderBuffer& buffer) override {
+      const media::DecoderBuffer& buffer) override {
     if (!buffer.is_key_frame()) {
       return current_resolution_;
     }
 
-    Vp8Parser parser;
-    Vp8FrameHeader frame_header;
+    media::Vp8Parser parser;
+    media::Vp8FrameHeader frame_header;
     if (!parser.ParseFrame(buffer.data(), buffer.data_size(), &frame_header)) {
       DLOG(ERROR) << "Failed to parse vp8 stream";
       current_resolution_ = absl::nullopt;
@@ -42,7 +45,7 @@
 
     return current_resolution_;
   }
-  VideoCodec codec() const override { return media::VideoCodec::kVP8; }
+  media::VideoCodec codec() const override { return media::VideoCodec::kVP8; }
 
  private:
   absl::optional<gfx::Size> current_resolution_;
@@ -55,7 +58,7 @@
   ~Vp9ResolutionMonitor() override = default;
 
   absl::optional<gfx::Size> GetResolution(
-      const DecoderBuffer& buffer) override {
+      const media::DecoderBuffer& buffer) override {
     std::vector<uint32_t> frame_sizes;
     if (buffer.has_side_data()) {
       frame_sizes = buffer.side_data()->spatial_layers;
@@ -78,21 +81,21 @@
     return parse_error ? absl::nullopt : max_resolution;
   }
 
-  VideoCodec codec() const override { return media::VideoCodec::kVP9; }
+  media::VideoCodec codec() const override { return media::VideoCodec::kVP9; }
 
  private:
   bool GetNextFrameSize(gfx::Size& frame_size, bool& parse_error) {
-    Vp9FrameHeader frame_header;
+    media::Vp9FrameHeader frame_header;
     gfx::Size allocate_size;
-    Vp9Parser::Result result = parser_.ParseNextFrame(
+    media::Vp9Parser::Result result = parser_.ParseNextFrame(
         &frame_header, &allocate_size, /*frame_decrypt_config=*/nullptr);
     switch (result) {
-      case Vp9Parser::Result::kOk:
+      case media::Vp9Parser::Result::kOk:
         frame_size.SetSize(frame_header.frame_width, frame_header.frame_height);
         return true;
-      case Vp9Parser::Result::kEOStream:
+      case media::Vp9Parser::Result::kEOStream:
         return false;
-      case Vp9Parser::Result::kInvalidStream:
+      case media::Vp9Parser::Result::kInvalidStream:
         DLOG(ERROR) << "Failed parsing vp9 frame";
         parse_error = true;
         return false;
@@ -100,7 +103,7 @@
     NOTREACHED_NORETURN() << "Unexpected result: " << static_cast<int>(result);
   }
 
-  Vp9Parser parser_;
+  media::Vp9Parser parser_;
 };
 
 class Av1ResolutionMonitor : public ResolutionMonitor {
@@ -116,7 +119,7 @@
   ~Av1ResolutionMonitor() override = default;
 
   absl::optional<gfx::Size> GetResolution(
-      const DecoderBuffer& buffer) override {
+      const media::DecoderBuffer& buffer) override {
     auto parser = base::WrapUnique(new (std::nothrow) libgav1::ObuParser(
         buffer.data(), buffer.data_size(), kDefaultOperatingPoint,
         &buffer_pool_, &decoder_state_));
@@ -161,7 +164,7 @@
     return max_resolution;
   }
 
-  VideoCodec codec() const override { return VideoCodec::kAV1; }
+  media::VideoCodec codec() const override { return media::VideoCodec::kAV1; }
 
  private:
   // Returns true iff the current decode sequence has multiple spatial layers.
@@ -224,69 +227,45 @@
   ~H264ResolutionMonitor() override = default;
 
   absl::optional<gfx::Size> GetResolution(
-      const DecoderBuffer& buffer) override {
+      const media::DecoderBuffer& buffer) override {
     if (!buffer.is_key_frame()) {
       return current_resolution_;
     }
 
-    H264Parser h264_parser;
-    h264_parser.SetStream(buffer.data(),
-                          base::checked_cast<off_t>(buffer.data_size()));
-    size_t nalus_count = 0;
-    H264NALU nalu;
     absl::optional<gfx::Size> resolution;
-    bool parse_error = false;
-    while (GetNextNALU(h264_parser, nalu, parse_error)) {
-      nalus_count += 1;
-      if (nalu.nal_unit_type == H264NALU::Type::kSPS) {
-        int sps_id = 0;
-        if (h264_parser.ParseSPS(&sps_id) != H264Parser::kOk) {
-          DLOG(ERROR) << "Failed parsing h264 SPS";
+    std::vector<webrtc::H264::NaluIndex> nalu_indices =
+        webrtc::H264::FindNaluIndices(buffer.data(), buffer.data_size());
+    for (const auto& nalu_index : nalu_indices) {
+      base::span<const uint8_t> nalu_payload(
+          buffer.data() + nalu_index.payload_start_offset,
+          nalu_index.payload_size);
+      if (webrtc::H264::ParseNaluType(nalu_payload[0]) ==
+          webrtc::H264::NaluType::kSps) {
+        if (nalu_payload.size() < webrtc::H264::kNaluTypeSize + 1) {
+          DLOG(ERROR) << "H.264 SPS NALU size too small for parsing.";
           return absl::nullopt;
         }
-        const H264SPS* sps = h264_parser.GetSPS(sps_id);
-        if (!sps) {
-          DLOG(ERROR) << "Failed getting h264 SPS: id=" << sps_id;
+        // Parse without NALU header.
+        absl::optional<webrtc::SpsParser::SpsState> sps =
+            webrtc::SpsParser::ParseSps(
+                nalu_payload.data() + webrtc::H264::kNaluTypeSize,
+                nalu_payload.size() - webrtc::H264::kNaluTypeSize);
+        if (!sps || !sps->width || !sps->height) {
+          DLOG(ERROR) << "Failed parsing H.264 SPS.";
           return absl::nullopt;
         }
-        absl::optional<gfx::Size> coded_size = sps->GetCodedSize();
-        if (!coded_size) {
-          DLOG(ERROR) << "Failed getting coded size";
-          return absl::nullopt;
-        }
-        resolution = *coded_size;
+        resolution = gfx::Size(sps->width, sps->height);
+        break;
       }
     }
 
-    if (parse_error || nalus_count == 0) {
-      return absl::nullopt;
-    }
-
     current_resolution_ = resolution;
     return current_resolution_;
   }
 
-  VideoCodec codec() const override { return VideoCodec::kH264; }
+  media::VideoCodec codec() const override { return media::VideoCodec::kH264; }
 
  private:
-  bool GetNextNALU(H264Parser& h264_parser,
-                   H264NALU& nalu,
-                   bool& parse_error) const {
-    H264Parser::Result result = h264_parser.AdvanceToNextNALU(&nalu);
-    switch (result) {
-      case H264Parser::Result::kOk:
-        return true;
-      case H264Parser::Result::kEOStream:
-        return false;
-      case H264Parser::Result::kInvalidStream:
-      case H264Parser::Result::kUnsupportedStream:
-        DLOG(ERROR) << "Failed parsing h264 NALU";
-        parse_error = true;
-        return false;
-    }
-    NOTREACHED_NORETURN() << "Unexpected result: " << static_cast<int>(result);
-  }
-
   absl::optional<gfx::Size> current_resolution_;
 };
 }  // namespace
@@ -294,20 +273,21 @@
 ResolutionMonitor::~ResolutionMonitor() = default;
 
 // static
-std::unique_ptr<ResolutionMonitor> ResolutionMonitor::Create(VideoCodec codec) {
+std::unique_ptr<ResolutionMonitor> ResolutionMonitor::Create(
+    media::VideoCodec codec) {
   switch (codec) {
-    case VideoCodec::kH264:
+    case media::VideoCodec::kH264:
       return std::make_unique<H264ResolutionMonitor>();
-    case VideoCodec::kVP8:
+    case media::VideoCodec::kVP8:
       return std::make_unique<Vp8ResolutionMonitor>();
-    case VideoCodec::kVP9:
+    case media::VideoCodec::kVP9:
       return std::make_unique<Vp9ResolutionMonitor>();
-    case VideoCodec::kAV1:
+    case media::VideoCodec::kAV1:
       return std::make_unique<Av1ResolutionMonitor>();
-    // TODO(bugs.webrtc.org/13485): Add H265.
     default:
-      DLOG(ERROR) << "Unsupported codec: " << GetCodecName(codec);
+      DLOG(ERROR) << "Unsupported codec: " << media::GetCodecName(codec);
       return nullptr;
   }
 }
-}  // namespace media
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/peerconnection/resolution_monitor.h b/third_party/blink/renderer/platform/peerconnection/resolution_monitor.h
new file mode 100644
index 0000000..c8537129
--- /dev/null
+++ b/third_party/blink/renderer/platform/peerconnection/resolution_monitor.h
@@ -0,0 +1,39 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_PEERCONNECTION_RESOLUTION_MONITOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_PEERCONNECTION_RESOLUTION_MONITOR_H_
+
+#include <memory>
+
+#include "base/sequence_checker.h"
+#include "media/base/video_codecs.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace media {
+class DecoderBuffer;
+}  // namespace media
+
+namespace blink {
+
+// Resolution monitor acquires a resolution of DecoderBuffer by parsing it. We
+// can know the stream resolution before the first keyframe is decoded. This
+// avoids requesting a sender to produce a keyframe again when a software
+// decoder fallback happens due to stream resolution change.
+class PLATFORM_EXPORT ResolutionMonitor {
+ public:
+  virtual ~ResolutionMonitor();
+
+  static std::unique_ptr<ResolutionMonitor> Create(media::VideoCodec codec);
+
+  virtual absl::optional<gfx::Size> GetResolution(
+      const media::DecoderBuffer& buffer) = 0;
+  virtual media::VideoCodec codec() const = 0;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_PEERCONNECTION_RESOLUTION_MONITOR_H_
diff --git a/media/filters/resolution_monitor_unittest.cc b/third_party/blink/renderer/platform/peerconnection/resolution_monitor_unittest.cc
similarity index 60%
rename from media/filters/resolution_monitor_unittest.cc
rename to third_party/blink/renderer/platform/peerconnection/resolution_monitor_unittest.cc
index ecc7dcb8..43b524f 100644
--- a/media/filters/resolution_monitor_unittest.cc
+++ b/third_party/blink/renderer/platform/peerconnection/resolution_monitor_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "media/filters/resolution_monitor.h"
+#include "third_party/blink/renderer/platform/peerconnection/resolution_monitor.h"
 
 #include "base/files/file_util.h"
 #include "media/base/decoder_buffer.h"
@@ -10,24 +10,25 @@
 #include "media/filters/ivf_parser.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-namespace media {
+namespace blink {
+
 namespace {
-const VideoCodec kCodecs[] = {
-    VideoCodec::kH264,
-    VideoCodec::kVP8,
-    VideoCodec::kVP9,
-    VideoCodec::kAV1,
+const media::VideoCodec kCodecs[] = {
+    media::VideoCodec::kH264,
+    media::VideoCodec::kVP8,
+    media::VideoCodec::kVP9,
+    media::VideoCodec::kAV1,
 };
 
 class ResolutionMonitorTestWithInvalidFrame
-    : public ::testing::TestWithParam<VideoCodec> {
+    : public ::testing::TestWithParam<media::VideoCodec> {
  protected:
   std::string kInvalidData = "This is invalid data and causes a parser error";
 };
 
 TEST_P(ResolutionMonitorTestWithInvalidFrame, ReturnNullOpt) {
-  const VideoCodec codec = GetParam();
-  auto invalid_buffer = DecoderBuffer::CopyFrom(
+  const media::VideoCodec codec = GetParam();
+  auto invalid_buffer = media::DecoderBuffer::CopyFrom(
       reinterpret_cast<const uint8_t*>(kInvalidData.data()),
       kInvalidData.size());
   invalid_buffer->set_is_key_frame(true);
@@ -43,7 +44,7 @@
 
 struct FrameData {
   std::string file_name;
-  VideoCodec codec;
+  media::VideoCodec codec;
   gfx::Size resolution;
 };
 
@@ -52,7 +53,7 @@
 
 TEST_P(ResolutionMonitorTestWithValidFrame, ReturnExpectedResolution) {
   const auto param = GetParam();
-  auto buffer = ReadTestDataFile(param.file_name);
+  auto buffer = media::ReadTestDataFile(param.file_name);
   ASSERT_TRUE(buffer);
   buffer->set_is_key_frame(true);
 
@@ -62,30 +63,30 @@
 }
 
 const FrameData kH264Frames[] = {
-    // 320x192 because we acquire coded size.
-    {"h264-320x180-frame-0", VideoCodec::kH264, gfx::Size(320, 192)},
-    {"bear-320x192-baseline-frame-0.h264", VideoCodec::kH264,
+    // 320x180 because we acquire visible size here.
+    {"h264-320x180-frame-0", media::VideoCodec::kH264, gfx::Size(320, 180)},
+    {"bear-320x192-baseline-frame-0.h264", media::VideoCodec::kH264,
      gfx::Size(320, 192)},
-    {"bear-320x192-high-frame-0.h264", VideoCodec::kH264, gfx::Size(320, 192)},
+    {"bear-320x192-high-frame-0.h264", media::VideoCodec::kH264, gfx::Size(320, 192)},
 };
 
 const FrameData kVP8Frames[] = {
-    {"vp8-I-frame-160x240", VideoCodec::kVP8, gfx::Size(160, 240)},
-    {"vp8-I-frame-320x120", VideoCodec::kVP8, gfx::Size(320, 120)},
-    {"vp8-I-frame-320x240", VideoCodec::kVP8, gfx::Size(320, 240)},
-    {"vp8-I-frame-320x480", VideoCodec::kVP8, gfx::Size(320, 480)},
-    {"vp8-I-frame-640x240", VideoCodec::kVP8, gfx::Size(640, 240)},
+    {"vp8-I-frame-160x240", media::VideoCodec::kVP8, gfx::Size(160, 240)},
+    {"vp8-I-frame-320x120", media::VideoCodec::kVP8, gfx::Size(320, 120)},
+    {"vp8-I-frame-320x240", media::VideoCodec::kVP8, gfx::Size(320, 240)},
+    {"vp8-I-frame-320x480", media::VideoCodec::kVP8, gfx::Size(320, 480)},
+    {"vp8-I-frame-640x240", media::VideoCodec::kVP8, gfx::Size(640, 240)},
 };
 
 const FrameData kVP9Frames[] = {
-    {"vp9-I-frame-1280x720", VideoCodec::kVP9, gfx::Size(1280, 720)},
-    {"vp9-I-frame-320x240", VideoCodec::kVP9, gfx::Size(320, 240)},
+    {"vp9-I-frame-1280x720", media::VideoCodec::kVP9, gfx::Size(1280, 720)},
+    {"vp9-I-frame-320x240", media::VideoCodec::kVP9, gfx::Size(320, 240)},
 };
 
 const FrameData kAV1Frames[] = {
-    {"av1-I-frame-320x240", VideoCodec::kAV1, gfx::Size(320, 240)},
-    {"av1-I-frame-1280x720", VideoCodec::kAV1, gfx::Size(1280, 720)},
-    {"av1-monochrome-I-frame-320x240-8bpp", VideoCodec::kAV1,
+    {"av1-I-frame-320x240", media::VideoCodec::kAV1, gfx::Size(320, 240)},
+    {"av1-I-frame-1280x720", media::VideoCodec::kAV1, gfx::Size(1280, 720)},
+    {"av1-monochrome-I-frame-320x240-8bpp", media::VideoCodec::kAV1,
      gfx::Size(320, 240)},
 };
 
@@ -102,22 +103,22 @@
                          ResolutionMonitorTestWithValidFrame,
                          ::testing::ValuesIn(kAV1Frames));
 
-std::vector<scoped_refptr<DecoderBuffer>> ReadIVF(const std::string& fname) {
+std::vector<scoped_refptr<media::DecoderBuffer>> ReadIVF(const std::string& fname) {
   std::string ivf_data;
-  auto input_file = GetTestDataFilePath(fname);
+  auto input_file = media::GetTestDataFilePath(fname);
   EXPECT_TRUE(base::ReadFileToString(input_file, &ivf_data));
 
-  IvfParser ivf_parser;
-  IvfFileHeader ivf_header{};
+  media::IvfParser ivf_parser;
+  media::IvfFileHeader ivf_header{};
   EXPECT_TRUE(
       ivf_parser.Initialize(reinterpret_cast<const uint8_t*>(ivf_data.data()),
                             ivf_data.size(), &ivf_header));
 
-  std::vector<scoped_refptr<DecoderBuffer>> buffers;
-  IvfFrameHeader ivf_frame_header{};
+  std::vector<scoped_refptr<media::DecoderBuffer>> buffers;
+  media::IvfFrameHeader ivf_frame_header{};
   const uint8_t* data;
   while (ivf_parser.ParseNextFrame(&ivf_frame_header, &data)) {
-    buffers.push_back(DecoderBuffer::CopyFrom(
+    buffers.push_back(media::DecoderBuffer::CopyFrom(
         reinterpret_cast<const uint8_t*>(data), ivf_frame_header.frame_size));
   }
   return buffers;
@@ -125,7 +126,7 @@
 
 struct VideoData {
   std::string file_name;
-  VideoCodec codec;
+  media::VideoCodec codec;
   gfx::Size resolution;
 };
 
@@ -144,20 +145,20 @@
 }
 
 const VideoData kVP8Videos[] = {
-    {"test-25fps.vp8", VideoCodec::kVP8, gfx::Size(320, 240)},
-    {"bear-1280x720.ivf", VideoCodec::kVP8, gfx::Size(1280, 720)},
+    {"test-25fps.vp8", media::VideoCodec::kVP8, gfx::Size(320, 240)},
+    {"bear-1280x720.ivf", media::VideoCodec::kVP8, gfx::Size(1280, 720)},
 };
 
 const VideoData kVP9Videos[] = {
-    {"test-25fps.vp9", VideoCodec::kVP9, gfx::Size(320, 240)},
-    {"test-25fps.vp9_2", VideoCodec::kVP9, gfx::Size(320, 240)},
-    {"bear-vp9.ivf", VideoCodec::kVP9, gfx::Size(320, 240)},
+    {"test-25fps.vp9", media::VideoCodec::kVP9, gfx::Size(320, 240)},
+    {"test-25fps.vp9_2", media::VideoCodec::kVP9, gfx::Size(320, 240)},
+    {"bear-vp9.ivf", media::VideoCodec::kVP9, gfx::Size(320, 240)},
 };
 
 const VideoData kAV1Videos[] = {
-    {"test-25fps.av1.ivf", VideoCodec::kAV1, gfx::Size(320, 240)},
-    {"av1-show_existing_frame.ivf", VideoCodec::kAV1, gfx::Size(208, 144)},
-    {"av1-svc-L1T2.ivf", VideoCodec::kAV1, gfx::Size(640, 360)},
+    {"test-25fps.av1.ivf", media::VideoCodec::kAV1, gfx::Size(320, 240)},
+    {"av1-show_existing_frame.ivf", media::VideoCodec::kAV1, gfx::Size(208, 144)},
+    {"av1-svc-L1T2.ivf", media::VideoCodec::kAV1, gfx::Size(640, 360)},
 };
 
 INSTANTIATE_TEST_SUITE_P(VP8,
@@ -169,5 +170,7 @@
 INSTANTIATE_TEST_SUITE_P(AV1,
                          ResolutionMonitorTestWithValidVideo,
                          ::testing::ValuesIn(kAV1Videos));
+
 }  // namespace
-}  // namespace media
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
index ca9ea29..a3aebc4 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.cc
@@ -494,7 +494,7 @@
 std::unique_ptr<RTCVideoDecoderAdapter> RTCVideoDecoderAdapter::Create(
     media::GpuVideoAcceleratorFactories* gpu_factories,
     const webrtc::SdpVideoFormat& format,
-    std::unique_ptr<media::ResolutionMonitor> resolution_monitor) {
+    std::unique_ptr<ResolutionMonitor> resolution_monitor) {
   DVLOG(1) << __func__ << "(" << format.name << ")";
 
   const webrtc::VideoCodecType video_codec_type =
@@ -514,7 +514,7 @@
   config.set_is_rtc(true);
 
   if (!resolution_monitor) {
-    resolution_monitor = media::ResolutionMonitor::Create(config.codec());
+    resolution_monitor = ResolutionMonitor::Create(config.codec());
     if (!resolution_monitor) {
       DLOG(ERROR) << "Failed to create ResolutionMonitor for codec: "
                   << media::GetCodecName(config.codec());
@@ -544,7 +544,7 @@
 RTCVideoDecoderAdapter::RTCVideoDecoderAdapter(
     media::GpuVideoAcceleratorFactories* gpu_factories,
     const media::VideoDecoderConfig& config,
-    std::unique_ptr<media::ResolutionMonitor> resolution_monitor)
+    std::unique_ptr<ResolutionMonitor> resolution_monitor)
     : media_task_runner_(gpu_factories->GetTaskRunner()),
       config_(config),
       resolution_monitor_(std::move(resolution_monitor)) {
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
index 23b2eb5..acb8aa8 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h
@@ -16,7 +16,7 @@
 #include "build/build_config.h"
 #include "media/base/decoder.h"
 #include "media/base/video_decoder_config.h"
-#include "media/filters/resolution_monitor.h"
+#include "third_party/blink/renderer/platform/peerconnection/resolution_monitor.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_decoder.h"
@@ -65,7 +65,7 @@
   static std::unique_ptr<RTCVideoDecoderAdapter> Create(
       media::GpuVideoAcceleratorFactories* gpu_factories,
       const webrtc::SdpVideoFormat& format,
-      std::unique_ptr<media::ResolutionMonitor> resolution_monitor = nullptr);
+      std::unique_ptr<ResolutionMonitor> resolution_monitor = nullptr);
 
   RTCVideoDecoderAdapter(const RTCVideoDecoderAdapter&) = delete;
   RTCVideoDecoderAdapter& operator=(const RTCVideoDecoderAdapter&) = delete;
@@ -108,10 +108,9 @@
   };
 
   // Called on the worker thread.
-  RTCVideoDecoderAdapter(
-      media::GpuVideoAcceleratorFactories* gpu_factories,
-      const media::VideoDecoderConfig& config,
-      std::unique_ptr<media::ResolutionMonitor> resolution_monitor);
+  RTCVideoDecoderAdapter(media::GpuVideoAcceleratorFactories* gpu_factories,
+                         const media::VideoDecoderConfig& config,
+                         std::unique_ptr<ResolutionMonitor> resolution_monitor);
 
   bool InitializeSync(const media::VideoDecoderConfig& config);
   absl::optional<DecodeResult> DecodeInternal(
@@ -130,7 +129,7 @@
   // Construction parameters.
   media::VideoDecoderConfig config_;
 
-  const std::unique_ptr<media::ResolutionMonitor> resolution_monitor_
+  const std::unique_ptr<ResolutionMonitor> resolution_monitor_
       GUARDED_BY_CONTEXT(decoding_sequence_checker_);
 
   // Decoding thread members.
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
index 4c57b01..d4d6119 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter_test.cc
@@ -29,10 +29,10 @@
 #include "media/base/video_decoder_config.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
-#include "media/filters/resolution_monitor.h"
 #include "media/video/mock_gpu_video_accelerator_factories.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/platform/peerconnection/resolution_monitor.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h"
 #include "third_party/blink/renderer/platform/webrtc/webrtc_video_utils.h"
 #include "third_party/webrtc/api/video_codecs/video_codec.h"
@@ -52,7 +52,7 @@
 
 namespace {
 
-class FakeResolutionMonitor : public media::ResolutionMonitor {
+class FakeResolutionMonitor : public ResolutionMonitor {
  public:
   explicit FakeResolutionMonitor(bool pass_resolution_monitor,
                                  const webrtc::SdpVideoFormat& format)
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 9b4465f..21dd205 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -327,6 +327,10 @@
       base_feature: "none",
     },
     {
+      name: "AppTitle",
+      status: "experimental",
+    },
+    {
       // crbug.com/484651
       name: "ArrowKeysInVerticalWritingModes",
       status: "stable",
@@ -2419,7 +2423,7 @@
     {
       name: "MediaSessionEnterPictureInPicture",
       public: true,
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "MediaSessionSlides",
@@ -4265,7 +4269,7 @@
     },
     {
       name: "WebGLDrawingBufferStorage",
-      status: "experimental",
+      status: "stable",
       base_feature: "none",
     },
     {
diff --git a/third_party/blink/renderer/platform/scheduler/BUILD.gn b/third_party/blink/renderer/platform/scheduler/BUILD.gn
index 04beb3b..34291347 100644
--- a/third_party/blink/renderer/platform/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/platform/scheduler/BUILD.gn
@@ -44,7 +44,6 @@
     "common/simple_main_thread_scheduler.h",
     "common/single_thread_idle_task_runner.cc",
     "common/single_thread_idle_task_runner.h",
-    "common/task_attribution_info.cc",
     "common/task_priority.cc",
     "common/task_priority.h",
     "common/thread.cc",
diff --git a/third_party/blink/renderer/platform/scheduler/common/task_attribution_info.cc b/third_party/blink/renderer/platform/scheduler/common/task_attribution_info.cc
deleted file mode 100644
index 57738d0..0000000
--- a/third_party/blink/renderer/platform/scheduler/common/task_attribution_info.cc
+++ /dev/null
@@ -1,21 +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 "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
-
-namespace blink::scheduler {
-
-void TaskAttributionInfo::Dispose() {
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
-  if (!tracker) {
-    return;
-  }
-  if (auto* observer = tracker->GetObserverForTaskDisposal(task_id_)) {
-    observer->OnTaskDisposal(*this);
-  }
-}
-
-}  // namespace blink::scheduler
diff --git a/third_party/blink/renderer/platform/scheduler/common/tracing_helper.cc b/third_party/blink/renderer/platform/scheduler/common/tracing_helper.cc
index 07d3a38..6fed7dc8 100644
--- a/third_party/blink/renderer/platform/scheduler/common/tracing_helper.cc
+++ b/third_party/blink/renderer/platform/scheduler/common/tracing_helper.cc
@@ -196,9 +196,9 @@
           TASK_TYPE_MAIN_THREAD_TASK_QUEUE_IPC_TRACKING;
     case TaskType::kNetworkingUnfreezable:
       return RendererMainThreadTaskExecution::TASK_TYPE_NETWORKING_UNFREEZABLE;
-    case TaskType::kNetworkingUnfreezableImageLoading:
+    case TaskType::kNetworkingUnfreezableRenderBlockingLoading:
       return RendererMainThreadTaskExecution::
-          TASK_TYPE_NETWORKING_UNFREEZABLE_IMAGE_LOADING;
+          TASK_TYPE_NETWORKING_UNFREEZABLE_RENDER_BLOCKING_LOADING;
     case TaskType::kWakeLock:
       return RendererMainThreadTaskExecution::TASK_TYPE_WAKE_LOCK;
     case TaskType::kInternalInputBlocking:
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
index f604eda..7074f8e 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.cc
@@ -448,7 +448,7 @@
       return IsInflightNetworkRequestBackForwardCacheSupportEnabled()
                  ? UnfreezableLoadingTaskQueueTraits()
                  : LoadingTaskQueueTraits();
-    case TaskType::kNetworkingUnfreezableImageLoading: {
+    case TaskType::kNetworkingUnfreezableRenderBlockingLoading: {
       QueueTraits queue_traits =
           IsInflightNetworkRequestBackForwardCacheSupportEnabled()
               ? UnfreezableLoadingTaskQueueTraits()
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
index 4e151825..a0a376d 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl_unittest.cc
@@ -122,7 +122,7 @@
     TaskType::kInternalLoading,
     TaskType::kNetworking,
     TaskType::kNetworkingUnfreezable,
-    TaskType::kNetworkingUnfreezableImageLoading,
+    TaskType::kNetworkingUnfreezableRenderBlockingLoading,
     TaskType::kNetworkingControl,
     TaskType::kLowPriorityScriptExecution,
     TaskType::kDOMManipulation,
@@ -1453,9 +1453,9 @@
             TaskPriority::kHighestPriority);
 }
 
-TEST_F(FrameSchedulerImplTest, RenderBlockingImageLoading) {
+TEST_F(FrameSchedulerImplTest, RenderBlockingRenderBlockingLoading) {
   auto render_blocking_task_queue =
-      GetTaskQueue(TaskType::kNetworkingUnfreezableImageLoading);
+      GetTaskQueue(TaskType::kNetworkingUnfreezableRenderBlockingLoading);
   page_scheduler_->SetPageVisible(false);
   EXPECT_EQ(render_blocking_task_queue->GetQueuePriority(),
             TaskPriority::kNormalPriority);
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
index a9ebb170..bc7f2ad9 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl_unittest.cc
@@ -491,7 +491,7 @@
     prioritised_local_frame_task_runner_ = main_frame_scheduler_->GetTaskRunner(
         blink::TaskType::kInternalHighPriorityLocalFrame);
     render_blocking_task_runner_ = main_frame_scheduler_->GetTaskRunner(
-        blink::TaskType::kNetworkingUnfreezableImageLoading);
+        blink::TaskType::kNetworkingUnfreezableRenderBlockingLoading);
   }
 
   MainThreadTaskQueue* compositor_task_queue() {
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc b/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
index db08540..2fa51954 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.cc
@@ -23,8 +23,8 @@
       return "Networking";
     case TaskType::kNetworkingUnfreezable:
       return "NetworkingUnfreezable";
-    case TaskType::kNetworkingUnfreezableImageLoading:
-      return "NetworkingUnfreezableImageLoading";
+    case TaskType::kNetworkingUnfreezableRenderBlockingLoading:
+      return "NetworkingUnfreezableRenderBlockingLoading";
     case TaskType::kNetworkingControl:
       return "NetworkingControl";
     case TaskType::kLowPriorityScriptExecution:
diff --git a/third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h b/third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h
index 4c005447..73867d90 100644
--- a/third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h
+++ b/third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h
@@ -8,15 +8,12 @@
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
-#include "third_party/blink/renderer/platform/heap/prefinalizer.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 
 namespace blink::scheduler {
 
 class TaskAttributionInfo final : public GarbageCollected<TaskAttributionInfo> {
-  USING_PRE_FINALIZER(TaskAttributionInfo, Dispose);
-
  public:
   TaskAttributionInfo(TaskAttributionId task_id, TaskAttributionInfo* parent)
       : task_id_(task_id),
@@ -25,7 +22,6 @@
 
   ~TaskAttributionInfo() = default;
 
-  PLATFORM_EXPORT void Dispose();
   TaskAttributionId Id() const { return task_id_; }
   TaskAttributionInfo* Parent() const { return parent_.Get(); }
   bool MaxChainLengthReached() const {
diff --git a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
index 0efdcde..65d7ef1 100644
--- a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
+++ b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
@@ -53,7 +53,6 @@
   class Observer : public GarbageCollectedMixin {
    public:
     virtual void OnCreateTaskScope(TaskAttributionInfo&, ScriptState*) = 0;
-    virtual void OnTaskDisposal(const TaskAttributionInfo&) = 0;
     virtual ExecutionContext* GetExecutionContext() = 0;
   };
 
@@ -99,8 +98,6 @@
   virtual void ResetSameDocumentNavigationTasks() = 0;
   virtual TaskAttributionInfo* CommitSameDocumentNavigation(
       TaskAttributionId) = 0;
-  virtual Observer* GetObserverForTaskDisposal(TaskAttributionId) = 0;
-  virtual void SetObserverForTaskDisposal(TaskAttributionId, Observer*) = 0;
 };
 
 }  // namespace blink::scheduler
diff --git a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
index b2568b36..5aa78605 100644
--- a/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/worker/worker_scheduler_impl.cc
@@ -217,7 +217,7 @@
       // Get(LocalFrame). (https://crbug.com/670534)
       return unpausable_task_queue_->CreateTaskRunner(type);
     case TaskType::kNetworkingUnfreezable:
-    case TaskType::kNetworkingUnfreezableImageLoading:
+    case TaskType::kNetworkingUnfreezableRenderBlockingLoading:
       return IsInflightNetworkRequestBackForwardCacheSupportEnabled()
                  ? unpausable_task_queue_->CreateTaskRunner(type)
                  : pausable_non_vt_task_queue_->CreateTaskRunner(type);
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite b/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
index 78ea935..18a7296f4 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
+++ b/third_party/blink/web_tests/FlagExpectations/enable-skia-graphite
@@ -1,3 +1,9 @@
 # tags: [ Android Fuchsia Linux Mac Mac11 Mac12 Mac13 Mac13-arm64 Win Win10.20h2 Win11 Win11-arm64 ]
 # tags: [ Release Debug ]
 # results: [ Timeout Crash Pass Failure Slow Skip ]
+
+# ====== New tests from wpt-importer added here ======
+crbug.com/626703 external/wpt/css/css-masking/mask-image/mask-opacity-1e.html [ Failure ]
+crbug.com/626703 external/wpt/png/apng/acTL-plays-two.html [ Timeout ]
+crbug.com/626703 external/wpt/screen-capture/getdisplaymedia-capture-controller.https.window.html [ Crash ]
+crbug.com/626703 external/wpt/screen-capture/getdisplaymedia.https.html [ Crash ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e0a51de..2b906e4 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6657,6 +6657,9 @@
 # rebaseline tool reports this test is flaky
 crbug.com/1481730 [ Linux ] external/wpt/html/canvas/element/manual/wide-gamut-canvas/canvas-display-p3-drawImage-video.html [ Failure ]
 
+crbug.com/1481209 [ Mac ] http/tests/inspector-protocol/target/browser-auto-attach-tab.js [ Failure Pass Timeout ]
+crbug.com/1481209 [ Linux ] http/tests/inspector-protocol/target/browser-auto-attach-tab.js [ Failure Pass Timeout ]
+
 # Gardener 2023-10-09
 crbug.com/1476826 [ Win ] http/tests/xmlhttprequest/small-chunks-response-text.html [ Failure Pass ]
 
@@ -6744,10 +6747,8 @@
 crbug.com/1502794 external/wpt/html/semantics/invokers/invoketarget-on-video-behavior.tentative.html [ Failure Pass Timeout ]
 
 # Gardener 2023-11-17
-crbug.com/1503108 [ Debug Mac13-arm64 ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
-crbug.com/1503108 [ Mac11 Release ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
-crbug.com/1503108 [ Mac12 Release ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
-crbug.com/1503108 [ Mac13 ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
+crbug.com/1503108 [ Mac ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
+crbug.com/1503108 [ Win ] external/wpt/credential-management/fedcm-disconnect.sub.https.html [ Timeout ]
 crbug.com/1503051 [ Mac ] virtual/keepalive-in-browser-migration/external/wpt/fetch/metadata/generated/audioworklet.https.sub.html [ Failure Pass ]
 crbug.com/1503119 [ Mac11 ] external/wpt/webtransport/echo-large-bidirectional-streams.https.any.html [ Timeout ]
 
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 1f1d346..db35e55c 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1654,6 +1654,103 @@
     "expires": "Mar 1, 2024"
   },
   {
+    "prefix": "boost-image-set-loading-task-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "fast/dom/HTMLImageElement/image-sizes-js-change.html"
+    ],
+    "args": [
+      "--enable-features=BoostImageSetLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  {
+    "prefix": "boost-font-loading-task-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "external/wpt/css/css-fonts/font-display/font-display.html"
+    ],
+    "args": [
+      "--enable-features=BoostFontLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  {
+    "prefix": "boost-video-loading-task-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "media/video-src-change.html"
+    ],
+    "args": [
+      "--enable-features=BoostVideoLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  "Boost non render blocking style loading task priority.",
+  {
+    "prefix": "boost-non-render-blocking-style-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "external/wpt/css/cssom/HTMLLinkElement-load-event-002.html"
+    ],
+    "args": [
+      "--enable-features=BoostNonRenderBlockingStyleLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  "Boost render blocking style loading task priority.",
+  {
+    "prefix": "boost-render-blocking-style-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "external/wpt/css/cssom/cssimportrule.html"
+    ],
+    "args": [
+      "--enable-features=BoostRenderBlockingStyleLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  "Boosts all of the cases in go/boost-loading-task-priority",
+  {
+    "prefix": "boost-loading-task-priority",
+    "platforms": ["Linux"],
+    "bases": [
+      "external/wpt/css/cssom/cssimportrule.html",
+      "external/wpt/css/cssom/HTMLLinkElement-load-event-002.html",
+      "media/video-src-change.html",
+      "external/wpt/css/css-fonts/font-display/font-display.html",
+      "fast/dom/HTMLImageElement/image-sizes-js-change.html"
+    ],
+    "args": [
+      "--enable-features=BoostImageSetLoadingTaskPriority,BoostFontLoadingTaskPriority,BoostVideoLoadingTaskPriority,BoostNonRenderBlockingStyleLoadingTaskPriority,BoostRenderBlockingStyleLoadingTaskPriority"
+    ],
+    "owners": [
+      "pdr@chromium.org",
+      "shaseley@chromium.org"
+    ],
+    "expires": "May 1, 2024"
+  },
+  {
     "prefix": "browsing-topics",
     "platforms": ["Linux", "Mac", "Win"],
     "bases": [
diff --git a/third_party/blink/web_tests/custom-elements/element-internals-aria-element-reflection.html b/third_party/blink/web_tests/custom-elements/element-internals-aria-element-reflection.html
index a249467..52de41fd 100644
--- a/third_party/blink/web_tests/custom-elements/element-internals-aria-element-reflection.html
+++ b/third_party/blink/web_tests/custom-elements/element-internals-aria-element-reflection.html
@@ -7,13 +7,13 @@
     class CustomElement extends HTMLElement {
       constructor() {
         super();
-        this.i = this.attachInternals();
+        this.internals = this.attachInternals();
       }
     }
     customElements.define('custom-element', CustomElement);
   </script>
 
-  <custom-element></custom-element>
+  <custom-element id="custom1"></custom-element>
   <div id="activedescendant">Active descendant</div>
   <div id="controls">Controls</div>
   <div id="describedby">Described by</div>
@@ -36,27 +36,51 @@
       ['ariaOwnsElements', 'owns']];
 
     test(t => {
-      const custom = document.body.querySelector('custom-element');
+      const custom = document.getElementById('custom1');
       for (const [property, id] of element_properties) {
-        assert_equals(custom.i[property], null, `Could not retrieve ${property} from ElementInternals`);
+        assert_equals(custom.internals[property], null, `Could not retrieve ${property} from ElementInternals`);
       }
       for (const [property, id] of array_properties) {
-        assert_equals(custom.i[property], null, `Could not retrieve ${property} from ElementInternals`);
+        assert_equals(custom.internals[property], null, `Could not retrieve ${property} from ElementInternals`);
       }
     }, "Getting previously-unset ARIA element reflection properties on ElementInternals should return null, and not crash");
 
     test(t => {
-      const custom = document.body.querySelector('custom-element');
+      const custom = document.getElementById('custom1');
       for (const [property, id] of element_properties) {
         const related = document.getElementById(id);
-        custom.i[property] = related;
-        assert_equals(custom.i[property], related, `Could not retrieve ${property} from ElementInternals`);
+        custom.internals[property] = related;
+        assert_equals(custom.internals[property], related, `Could not retrieve ${property} from ElementInternals`);
       }
       for (const [property, id] of array_properties) {
         const related = document.getElementById(id);
-        custom.i[property] = [related];
-        assert_array_equals(custom.i[property], [related], `Could not retrieve ${property} from ElementInternals`);
+        custom.internals[property] = [related];
+        assert_array_equals(custom.internals[property], [related], `Could not retrieve ${property} from ElementInternals`);
       }
     }, "Getting ARIA element reflection properties on ElementInternals should return the value that was set, and not crash");
+
+    test(t => {
+      const custom = document.getElementById('custom1');
+      for (const [property, id] of element_properties) {
+        const related = document.getElementById(id);
+        custom.internals[property] = related;
+        assert_equals(custom.internals[property], related);
+
+        custom.internals[property] = null;
+        assert_equals(custom.internals[property], null, `Could not retrieve null ${property} from ElementInternals`);
+      }
+      for (const [property, id] of array_properties) {
+        const related = document.getElementById(id);
+        custom.internals[property] = [related];
+        assert_array_equals(custom.internals[property], [related]);
+
+        custom.internals[property] = [];
+        assert_array_equals(custom.internals[property], []);
+
+        custom.internals[property] = null;
+        assert_equals(custom.internals[property], null, `Could not retrieve ${property} from ElementInternals`);
+      }
+    }, "Setting ARIA element reflection properties on ElementInternals to null should delete any previous value, and not crash");
+
   </script>
-</body>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index f3b6686..34db7ce 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -63,6 +63,13 @@
        {}
       ]
      ],
+     "aria-owned-with-role-change.html": [
+      "c5b2130969c018dce682e2d1a404570f5c26f4e9",
+      [
+       null,
+       {}
+      ]
+     ],
      "aria-owns-destroyed-by-content-replacement.html": [
       "b64ebaa6024f50f68249204d21b60b1a2109179b",
       [
@@ -98,6 +105,13 @@
        {}
       ]
      ],
+     "aria-owns-with-role-change.html": [
+      "13e1eaff6dd097c06d6275ebc9fe6646850454bf",
+      [
+       null,
+       {}
+      ]
+     ],
      "bdo-table-cell.html": [
       "ae12541f8d8cb97248a7a7f3a3683f753aa96377",
       [
@@ -290321,15 +290335,15 @@
     ],
     "derive_bits_keys": {
      "cfrg_curves_bits.https.any-expected.txt": [
-      "6c24f78fa10b6691207b0fdcce14569b7d0c774c",
+      "f0efe8e79931b6f6de9581b8ef578162c704196d",
       []
      ],
      "cfrg_curves_bits.https.any.worker-expected.txt": [
-      "6c24f78fa10b6691207b0fdcce14569b7d0c774c",
+      "f0efe8e79931b6f6de9581b8ef578162c704196d",
       []
      ],
      "cfrg_curves_bits.js": [
-      "4e12ca0eb711fae5168eb1a17db756588ef4eab6",
+      "ef6905e574c1581d0e5397ba9d5f955acec41b2c",
       []
      ],
      "cfrg_curves_bits_fixtures.js": [
@@ -290357,7 +290371,7 @@
       []
      ],
      "ecdh_bits.js": [
-      "e52ffc6bfdb6f6d4655cfa14b835adbebd088a2a",
+      "cb9747a529fd53530df926ddc99759e5a9126ee9",
       []
      ],
      "ecdh_keys.js": [
@@ -290397,7 +290411,7 @@
       []
      ],
      "hkdf.js": [
-      "2bb58533473eb9997151b43d342b7ef9ec06701d",
+      "3903da5cddff941c9ff82b19b0967ba41f0737f5",
       []
      ],
      "hkdf_vectors.js": [
@@ -290477,7 +290491,7 @@
       []
      ],
      "pbkdf2.js": [
-      "0403f382e1479c7c5018e91aba2ceb31b89998dc",
+      "4e4ae79d800a402b7a0933b752da9a5fb151bc2f",
       []
      ],
      "pbkdf2_vectors.js": [
@@ -343827,7 +343841,7 @@
       []
      ],
      "cssstyledeclaration-csstext-all-shorthand-expected.txt": [
-      "073ac7545b4d7c5e365b37a90089ffb917bca3f4",
+      "49cc4f9fc9a173d6057e3640301d0a683acd0635",
       []
      ],
      "cssstyledeclaration-csstext-expected.txt": [
@@ -362204,6 +362218,10 @@
         "b450c530ad031e1e0dcefc456be0b1ed10e2cded",
         []
        ],
+       "dir-assorted.window-expected.txt": [
+        "3ca95eb87ec727376dcdce80a9e01209d19b1d6a",
+        []
+       ],
        "dir-shadow-01-ref.html": [
         "3462b908b3df0d5550e74fa2ea93e9b2ff959b9b",
         []
@@ -396268,11 +396286,11 @@
      []
     ],
     "elementwise_unary.https.any-expected.txt": [
-     "9fdb626319cca2f172ef8179356dab207e64da0f",
+     "a11e68d8b08a475139166b86754c7cadeb791cea",
      []
     ],
     "elementwise_unary.https.any.worker-expected.txt": [
-     "40d851efd058a1ed37ba820e91b638ca604c9eb6",
+     "ce037b1a3346ba752e8742e2e01a9a4ad740644f",
      []
     ],
     "elu.https.any-expected.txt": [
@@ -396300,11 +396318,11 @@
      []
     ],
     "hard_sigmoid.https.any-expected.txt": [
-     "a6dcfda56729e4c27d13cfacaaf6931d3144f5e5",
+     "17e72f17a0ccb9c6e2eb15a95c745ca94dd62d39",
      []
     ],
     "hard_sigmoid.https.any.worker-expected.txt": [
-     "3406fcea5517acf68af941a859d2dd7d72f51bf2",
+     "40e838b8951361508bf0ef73cbea05dd924119d3",
      []
     ],
     "hard_swish.https.any.worker-expected.txt": [
@@ -396312,11 +396330,11 @@
      []
     ],
     "idlharness.https.any-expected.txt": [
-     "f335321c701a7fdfe3c57de5a05025d51483b321",
+     "d1751d0346b82f35c9bbbbc572a55deb81bf17a8",
      []
     ],
     "idlharness.https.any.worker-expected.txt": [
-     "bfc4b9ac5a56439425525b43fd3c2c4c11c46e52",
+     "1ff95783a70afcc82c75f6eadbfdf5e8756af5c4",
      []
     ],
     "leaky_relu.https.any.worker-expected.txt": [
@@ -480471,6 +480489,13 @@
         "dom/observable/tentative/observable-event-target.any.worker.html",
         {}
        ]
+      ],
+      "observable-event-target.window.js": [
+       "77a137a3622e8dbbfb4c3f177f512c0cd509b509",
+       [
+        "dom/observable/tentative/observable-event-target.window.html",
+        {}
+       ]
       ]
      }
     },
@@ -500964,6 +500989,13 @@
       {}
      ]
     ],
+    "report-event-sandboxed-iframe.https.html": [
+     "7298f39e6945255de783362396fc89f9754b6ef0",
+     [
+      null,
+      {}
+     ]
+    ],
     "resize-lock-input.https.html": [
      "261c9a737eff4b85723214b7f4735b8fe9423a65",
      [
@@ -517200,9 +517232,9 @@
       ]
      ],
      "direct-from-seller-signals.https.window.js": [
-      "0ae1efd8d3780efe9ff1a0886e6f79d4c17c2558",
+      "339bc32ee564320d155f43e1a555847da7ec125b",
       [
-       "fledge/tentative/direct-from-seller-signals.https.window.html?1-5",
+       "fledge/tentative/direct-from-seller-signals.https.window.html?1-4",
        {
         "script_metadata": [
          [
@@ -517227,30 +517259,42 @@
          ],
          [
           "variant",
-          "?1-5"
+          "?1-4"
          ],
          [
           "variant",
-          "?6-10"
+          "?5-8"
          ],
          [
           "variant",
-          "?11-15"
+          "?9-12"
          ],
          [
           "variant",
-          "?16-20"
+          "?13-16"
          ],
          [
           "variant",
-          "?21-last"
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
          ]
         ],
         "timeout": "long"
        }
       ],
       [
-       "fledge/tentative/direct-from-seller-signals.https.window.html?11-15",
+       "fledge/tentative/direct-from-seller-signals.https.window.html?13-16",
        {
         "script_metadata": [
          [
@@ -517275,30 +517319,42 @@
          ],
          [
           "variant",
-          "?1-5"
+          "?1-4"
          ],
          [
           "variant",
-          "?6-10"
+          "?5-8"
          ],
          [
           "variant",
-          "?11-15"
+          "?9-12"
          ],
          [
           "variant",
-          "?16-20"
+          "?13-16"
          ],
          [
           "variant",
-          "?21-last"
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
          ]
         ],
         "timeout": "long"
        }
       ],
       [
-       "fledge/tentative/direct-from-seller-signals.https.window.html?16-20",
+       "fledge/tentative/direct-from-seller-signals.https.window.html?17-20",
        {
         "script_metadata": [
          [
@@ -517323,30 +517379,42 @@
          ],
          [
           "variant",
-          "?1-5"
+          "?1-4"
          ],
          [
           "variant",
-          "?6-10"
+          "?5-8"
          ],
          [
           "variant",
-          "?11-15"
+          "?9-12"
          ],
          [
           "variant",
-          "?16-20"
+          "?13-16"
          ],
          [
           "variant",
-          "?21-last"
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
          ]
         ],
         "timeout": "long"
        }
       ],
       [
-       "fledge/tentative/direct-from-seller-signals.https.window.html?21-last",
+       "fledge/tentative/direct-from-seller-signals.https.window.html?21-24",
        {
         "script_metadata": [
          [
@@ -517371,30 +517439,42 @@
          ],
          [
           "variant",
-          "?1-5"
+          "?1-4"
          ],
          [
           "variant",
-          "?6-10"
+          "?5-8"
          ],
          [
           "variant",
-          "?11-15"
+          "?9-12"
          ],
          [
           "variant",
-          "?16-20"
+          "?13-16"
          ],
          [
           "variant",
-          "?21-last"
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
          ]
         ],
         "timeout": "long"
        }
       ],
       [
-       "fledge/tentative/direct-from-seller-signals.https.window.html?6-10",
+       "fledge/tentative/direct-from-seller-signals.https.window.html?25-28",
        {
         "script_metadata": [
          [
@@ -517419,23 +517499,215 @@
          ],
          [
           "variant",
-          "?1-5"
+          "?1-4"
          ],
          [
           "variant",
-          "?6-10"
+          "?5-8"
          ],
          [
           "variant",
-          "?11-15"
+          "?9-12"
          ],
          [
           "variant",
-          "?16-20"
+          "?13-16"
          ],
          [
           "variant",
-          "?21-last"
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "fledge/tentative/direct-from-seller-signals.https.window.html?29-last",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/resources/testdriver.js"
+         ],
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/fledge-util.sub.js"
+         ],
+         [
+          "script",
+          "/common/subset-tests.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ],
+         [
+          "variant",
+          "?1-4"
+         ],
+         [
+          "variant",
+          "?5-8"
+         ],
+         [
+          "variant",
+          "?9-12"
+         ],
+         [
+          "variant",
+          "?13-16"
+         ],
+         [
+          "variant",
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "fledge/tentative/direct-from-seller-signals.https.window.html?5-8",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/resources/testdriver.js"
+         ],
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/fledge-util.sub.js"
+         ],
+         [
+          "script",
+          "/common/subset-tests.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ],
+         [
+          "variant",
+          "?1-4"
+         ],
+         [
+          "variant",
+          "?5-8"
+         ],
+         [
+          "variant",
+          "?9-12"
+         ],
+         [
+          "variant",
+          "?13-16"
+         ],
+         [
+          "variant",
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
+         ]
+        ],
+        "timeout": "long"
+       }
+      ],
+      [
+       "fledge/tentative/direct-from-seller-signals.https.window.html?9-12",
+       {
+        "script_metadata": [
+         [
+          "script",
+          "/resources/testdriver.js"
+         ],
+         [
+          "script",
+          "/common/utils.js"
+         ],
+         [
+          "script",
+          "resources/fledge-util.sub.js"
+         ],
+         [
+          "script",
+          "/common/subset-tests.js"
+         ],
+         [
+          "timeout",
+          "long"
+         ],
+         [
+          "variant",
+          "?1-4"
+         ],
+         [
+          "variant",
+          "?5-8"
+         ],
+         [
+          "variant",
+          "?9-12"
+         ],
+         [
+          "variant",
+          "?13-16"
+         ],
+         [
+          "variant",
+          "?17-20"
+         ],
+         [
+          "variant",
+          "?21-24"
+         ],
+         [
+          "variant",
+          "?25-28"
+         ],
+         [
+          "variant",
+          "?29-last"
          ]
         ],
         "timeout": "long"
@@ -557869,7 +558141,7 @@
         ]
        ],
        "dir-assorted.window.js": [
-        "038b3f78d4d373286da051a6240493d0dce12112",
+        "0d4e4b82d9b9531087ed631fd38d9eb6ab6215e0",
         [
          "html/dom/elements/global-attributes/dir-assorted.window.html",
          {}
@@ -585241,6 +585513,13 @@
       {}
      ]
     ],
+    "resized-image-not-reconsidered.html": [
+     "6e195b89f9ef5dfbfc126e3fe7170d00dc93376f",
+     [
+      null,
+      {}
+     ]
+    ],
     "same-origin-redirects.html": [
      "b5cf9da2d120d4e03852954057fc7171fad9901f",
      [
@@ -590119,7 +590398,7 @@
      ]
     ],
     "GUM-non-applicable-constraint.https.html": [
-     "3e9481bfa445cfb77556bfe4b63c74faeb9298b8",
+     "e9fd6665dcface83218869a0a0fd1284c12f924f",
      [
       null,
       {
@@ -590230,7 +590509,7 @@
      ]
     ],
     "MediaDevices-getSupportedConstraints.https.html": [
-     "453184a16919a63ea72068538bca4f7c038957cf",
+     "7d374b5336ea5c12d1bf6c6d65c1766ebd2b9347",
      [
       null,
       {}
@@ -590397,7 +590676,7 @@
      ]
     ],
     "MediaStreamTrack-getCapabilities.https.html": [
-     "b67a8d51561a3d2c33047780014d890c1c2682a4",
+     "7d600c0e1b97c905cbeb172d0aae0a33ed961c9e",
      [
       null,
       {
@@ -590406,7 +590685,7 @@
      ]
     ],
     "MediaStreamTrack-getSettings.https.html": [
-     "1bda4c748ac5a0c0711cc231710c18ed5afa7c25",
+     "3bae50c346dd3c83d15f37eca598b58838250c46",
      [
       null,
       {
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-anchor-click-handler.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-anchor-click-handler.https.html
index c3161fd..8ee1cb51 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-anchor-click-handler.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-anchor-click-handler.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -18,10 +19,13 @@
     {generator_api: 'fledge', automatic_beacon: true,
      origin: get_host_info().HTTPS_REMOTE_ORIGIN});
   const new_url = new URL("resources/dummy.html", location.href);
-  const beacon_data = "This is the second test's beacon data!";
-  const beacon_type = "reserved.top_navigation_commit";
+  let beacon_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the second test's beacon data!",
+    destination: ["buyer"],
+  }
 
-  await fencedframe.execute((new_url, beacon_data, beacon_type) => {
+  await fencedframe.execute((new_url, beacon_event) => {
     let a = document.createElement('a');
     a.textContent = "Click me!";
     a.href = new_url;
@@ -36,16 +40,11 @@
     // before the navigation happens. This test checks to make sure that the
     // data makes it to the correct place by the time the navigation commits.
     a.onclick = () => {
-      let beacon_event = {
-        eventType: beacon_type,
-        eventData: beacon_data,
-        destination: ["buyer"],
-      }
       window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
     };
     document.body.appendChild(a);
 
-  }, [new_url, beacon_data, beacon_type]);
+  }, [new_url, beacon_event]);
 
   // This will trigger the beacon data storing + navigation.
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
@@ -53,9 +52,7 @@
                .pointerUp()
                .send();
 
-  const beacon_initiator_origin =
-      await nextAutomaticBeacon(beacon_type, beacon_data);
-  assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
 
   // Leaving this fenced frame around for subsequent tests can lead to
   // flakiness.
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-click-handler.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-click-handler.https.html
index 0fe3fbb..31392fd 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-click-handler.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-click-handler.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -17,12 +18,8 @@
   const fencedframe = await attachFencedFrameContext(
       {generator_api: 'fledge', automatic_beacon: true,
        origin: get_host_info().HTTPS_REMOTE_ORIGIN});
-  const new_url = new URL("resources/dummy.html", location.href);
-  const start_beacon_data = "This is the start beacon data!";
-  const commit_beacon_data = "This is the commit beacon data!";
 
-  await fencedframe.execute(
-      (new_url, start_beacon_data, commit_beacon_data) => {
+  await fencedframe.execute(() => {
     // This tests that old automatic beacon data is overwritten in subsequent
     // calls to setReportEventDataForAutomaticBeacons().
     let start_beacon_event_old = {
@@ -31,34 +28,29 @@
       destination: ["buyer"],
     }
     window.fence.setReportEventDataForAutomaticBeacons(start_beacon_event_old);
-    addEventListener("click", (event) => {
-      let start_beacon_event = {
-        eventType: "reserved.top_navigation_start",
-        eventData: start_beacon_data,
-        destination: ["buyer"],
-      }
-      window.fence.setReportEventDataForAutomaticBeacons(start_beacon_event);
-      let commit_beacon_event = {
-        eventType: "reserved.top_navigation_commit",
-        eventData: commit_beacon_data,
-        destination: ["buyer"],
-      }
-      window.fence.setReportEventDataForAutomaticBeacons(commit_beacon_event);
-      window.open(new_url, "_blank");
-    });
-  }, [new_url, start_beacon_data, commit_beacon_data]);
+  });
+  const start_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "This is the start data",
+    destination: ["buyer"],
+  }
+  const commit_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the commit data",
+    destination: ["buyer"],
+  }
+  // This will only set the automatic beacon data when the fenced frame is
+  // clicked.
+  await setupAutomaticBeacon(fencedframe, [start_event, commit_event],
+      NavigationTrigger.Click);
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
                .pointerDown()
                .pointerUp()
                .send();
 
-  const beacon_1_initiator_origin = await nextAutomaticBeacon(
-      "reserved.top_navigation_start", start_beacon_data);
-  assert_equals(beacon_1_initiator_origin, get_host_info().HTTPS_ORIGIN);
-  const beacon_2_initiator_origin = await nextAutomaticBeacon(
-      "reserved.top_navigation_commit", commit_beacon_data);
-  assert_equals(beacon_2_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(start_event.eventType, start_event.eventData);
+  await verifyBeaconData(commit_event.eventType, commit_event.eventData);
 
   // Leaving this fenced frame around for subsequent tests can lead to
   // flakiness.
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-component-ad.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-component-ad.https.html
index 132b123..1b1ef27 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-component-ad.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-component-ad.https.html
@@ -8,6 +8,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -21,16 +23,14 @@
     headers: [["Allow-Fenced-Frame-Automatic-Beacons", "true"]]
   });
   const new_url = new URL("resources/close.html", location.href);
-  const beacon_type = "reserved.top_navigation_start";
-  const beacon_data = "this is the beacon data";
+  const beacon_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "this is the beacon data",
+    destination: ["buyer"],
+    crossOriginExposed: true,
+  }
 
-  await fencedframe.execute(async (new_url, beacon_type, beacon_data) => {
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: "this is the beacon data",
-      destination: ["buyer"],
-      crossOriginExposed: true,
-    }
+  await fencedframe.execute(async (new_url, beacon_event) => {
     window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
 
     // Add an ad component that will perform the top-level navigation.
@@ -41,7 +41,7 @@
         window.open(new_url);
       });
     }, [new_url]);
-  }, [new_url, beacon_type]);
+  }, [new_url, beacon_event]);
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
       .pointerDown()
@@ -49,9 +49,7 @@
       .send();
 
   // The component frame should not use the data set in its parent.
-  const expected_beacon_data = "<No data>";
-  const received_beacon_data = await nextAutomaticBeacon(
-          beacon_type, expected_beacon_data);
+  await verifyBeaconData(beacon_event.eventType, "<No data>");
 }, 'Automatic beacon in an ad component should send without data with a ' +
    'header opt-in.');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-false.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-false.https.html
index ccd86349..24440e4 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-false.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-false.https.html
@@ -9,6 +9,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -18,38 +20,22 @@
     generator_api: 'fledge',
     automatic_beacon: true,
   });
-  const new_url = new URL("resources/close.html", location.href);
-  const beacon_type = "reserved.top_navigation_start";
+  const beacon_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "this is the beacon data",
+    destination: ["buyer"],
+    crossOriginExposed: false,
+  }
 
-  await fencedframe.execute(async (new_url, beacon_type) => {
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: "this is the beacon data",
-      destination: ["buyer"],
-      crossOriginExposed: false,
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-
-    // Add a cross-origin iframe that will perform the top-level navigation.
-    const iframe = await attachIFrameContext({
-      origin: get_host_info().HTTPS_REMOTE_ORIGIN,
-      headers: [["Allow-Fenced-Frame-Automatic-Beacons", "true"]],
-    });
-    await iframe.execute(async (new_url) => {
-      addEventListener("click", (event) => {
-        window.open(new_url);
-      });
-    }, [new_url]);
-  }, [new_url, beacon_type]);
+  await setupAutomaticBeacon(fencedframe, [beacon_event],
+      "resources/close.html", NavigationTrigger.CrossOriginClick);
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
       .pointerDown()
       .pointerUp()
       .send();
 
-  const expected_beacon_data = "<No data>";
-  const received_beacon_data = await nextAutomaticBeacon(
-          "reserved.top_navigation_start", expected_beacon_data);
+  await verifyBeaconData(beacon_event.eventType, "<No data>");
 }, 'Automatic beacon in a cross-origin subframe should send without data ' +
    'when crossOrigin=false.');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-navigation.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-navigation.https.html
index 5a19117..c476e804 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-navigation.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-navigation.https.html
@@ -8,6 +8,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -17,30 +19,16 @@
     generator_api: 'fledge',
     automatic_beacon: true
   });
-  const new_url = new URL("resources/close.html", location.href);
-  const beacon_data = "this is the beacon data";
-  const beacon_type = "reserved.top_navigation_start";
 
-  await fencedframe.execute(async (new_url, beacon_data, beacon_type) => {
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: beacon_data,
-      destination: ["buyer"],
-      crossOriginExposed: true,
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+  let beacon_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "this is the beacon data",
+    destination: ["buyer"],
+    crossOriginExposed: true,
+  }
 
-    // Add a cross-origin iframe that will perform the top-level navigation.
-    const iframe = await attachIFrameContext({
-      origin: get_host_info().HTTPS_REMOTE_ORIGIN,
-      headers: [["Allow-Fenced-Frame-Automatic-Beacons", "true"]]
-    });
-    await iframe.execute(async (new_url) => {
-      addEventListener("click", (event) => {
-        window.open(new_url, "_blank");
-      });
-    }, [new_url]);
-  }, [new_url, beacon_data, beacon_type]);
+  await setupAutomaticBeacon(fencedframe, [beacon_event],
+      "resources/close.html", NavigationTrigger.CrossOriginClick);
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
       .pointerDown()
@@ -48,7 +36,7 @@
       .send();
 
   const received_beacon_data =
-      await nextAutomaticBeacon(beacon_type, beacon_data);
+      await nextAutomaticBeacon(beacon_event.eventType, beacon_event.eventData);
 }, 'Automatic beacon in a cross-origin subframe');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-data.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-data.https.html
index 1b57100..dd00721 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-data.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-data.https.html
@@ -8,6 +8,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -38,13 +40,8 @@
       .pointerUp()
       .send();
 
-  const expected_beacon_data = "<No data>";
-  const received_beacon_data =
-      await nextAutomaticBeacon(
-          "reserved.top_navigation_start", expected_beacon_data);
-  const received_beacon_data_commit =
-      await nextAutomaticBeacon(
-          "reserved.top_navigation_commit", expected_beacon_data);
+  await verifyBeaconData("reserved.top_navigation_start", "<No data>");
+  await verifyBeaconData("reserved.top_navigation_commit", "<No data>");
 }, 'Automatic beacon in a cross-origin subframe with no beacon data set');
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html
index 6d43a73..fa19d17 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html
@@ -8,6 +8,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -17,41 +19,26 @@
     generator_api: 'fledge',
     automatic_beacon: true
   });
-  const new_url = new URL("resources/close.html", location.href);
-  const beacon_data = "this is the beacon data";
-  const beacon_type = "reserved.top_navigation_start";
 
-  await fencedframe.execute(async (new_url, beacon_data, beacon_type) => {
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: beacon_data,
-      destination: ["buyer"],
-      crossOriginExposed: true,
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-
-    // Add a cross-origin iframe that will perform the top-level navigation.
-    // Do not set the 'Allow-Fenced-Frame-Automatic-Beacons' header to true.
-    const iframe = await attachIFrameContext({
-      origin: get_host_info().HTTPS_REMOTE_ORIGIN,
-      headers: [["Allow-Fenced-Frame-Automatic-Beacons", "false"]]
-    });
-    await iframe.execute(async (new_url) => {
-      addEventListener("click", (event) => {
-        window.open(new_url, "_blank");
-      });
-    }, [new_url]);
-  }, [new_url, beacon_data, beacon_type]);
+  let beacon_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "this is the beacon data",
+    destination: ["buyer"],
+    crossOriginExposed: true,
+  }
+  // Add a cross-origin iframe that will perform the top-level navigation.
+  // Do not set the 'Allow-Fenced-Frame-Automatic-Beacons' header to true.
+  await setupAutomaticBeacon(fencedframe, [beacon_event],
+      "resources/close.html", NavigationTrigger.CrossOriginClickNoOptIn,
+      "_blank");
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
       .pointerDown()
       .pointerUp()
       .send();
 
-  const timeout = new Promise(resolve => t.step_timeout(resolve, 1000));
-  const result = await Promise.race(
-      [nextAutomaticBeacon(beacon_type, beacon_data), timeout]);
-  assert_true(typeof result === "undefined");
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
+      t);
 }, 'Automatic beacon in a cross-origin subframe with no opt-in header should ' +
    'not send.');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-destination.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-destination.https.html
index c3fa3d6..696c17f 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-destination.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-destination.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -19,24 +20,13 @@
     automatic_beacon: true,
     origin: get_host_info().HTTPS_REMOTE_ORIGIN
   });
-  const new_url = new URL("resources/dummy.html", location.href);
-  const beacon_data = "This is the beacon data!";
-  const beacon_type = "reserved.top_navigation_commit";
 
-  await fencedframe.execute(
-    (new_url, beacon_data, beacon_type) => {
-      addEventListener("click", (event) => {
-        let beacon_event = {
-          eventType: beacon_type,
-          eventData: beacon_data,
-          destination: ["component-seller"],
-        };
-        window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-        window.open(new_url, "_blank");
-      });
-    },
-    [new_url, beacon_data, beacon_type]
-  );
+  let beacon_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the beacon data!",
+    destination: ["component-seller"],
+  };
+  await setupAutomaticBeacon(fencedframe, [beacon_event]);
 
   await actions
     .pointerMove(0, 0, { origin: fencedframe.element })
@@ -47,10 +37,7 @@
   // An automatic beacon should be sent out, but no data should be sent as part
   // of the beacon because the "buyer" destination was not specified in
   // setReportEventDataForAutomaticBeacons().
-  const expected_data = "<No data>";
-  const beacon_initiator_origin =
-      await nextAutomaticBeacon(beacon_type, expected_data);
-  assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(beacon_event.eventType, "<No data>");
 }, "Set and trigger an automatic beacon with no destination specified");
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-opt-in.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-opt-in.https.html
index 5a815a8..177a7c6 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-opt-in.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-no-opt-in.https.html
@@ -8,6 +8,8 @@
 <script src="/resources/testdriver.js"></script>
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -38,11 +40,8 @@
     .pointerDown()
     .pointerUp()
     .send();
-  const timeout = new Promise(resolve => t.step_timeout(resolve, 1000));
-  const result = await Promise.race(
-      [nextAutomaticBeacon("reserved.top_navigation_start", "<No data>"),
-       timeout]);
-  assert_true(typeof result === "undefined");
+  await verifyBeaconData("reserved.top_navigation_start", "<No data>", false,
+      t);
 }, "Automatic beacons will not send if the document does not opt in.");
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-shared-storage.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-shared-storage.https.html
index 093e55b..4ee1d0d0 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-shared-storage.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-shared-storage.https.html
@@ -10,6 +10,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -18,40 +19,26 @@
   const fencedframe = await attachFencedFrameContext(
       {generator_api: 'sharedstorage',
        origin: get_host_info().HTTPS_REMOTE_ORIGIN});
-  const new_url = new URL("resources/dummy.html", location.href);
-  const start_beacon_data = "This is the start beacon data!";
-  const commit_beacon_data = "This is the commit beacon data!";
 
-  await fencedframe.execute((new_url, start_beacon_data,
-      commit_beacon_data) => {
-    addEventListener("click", (event) => {
-      let start_beacon_event = {
-        eventType: "reserved.top_navigation_start",
-        eventData: start_beacon_data,
-        destination: ["shared-storage-select-url"],
-      }
-      window.fence.setReportEventDataForAutomaticBeacons(start_beacon_event);
-      let commit_beacon_event = {
-        eventType: "reserved.top_navigation_commit",
-        eventData: commit_beacon_data,
-        destination: ["shared-storage-select-url"],
-      }
-      window.fence.setReportEventDataForAutomaticBeacons(commit_beacon_event);
-      window.open(new_url, "_blank");
-    });
-  }, [new_url, start_beacon_data, commit_beacon_data]);
+  let start_event = {
+    eventType: "reserved.top_navigation_start",
+    eventData: "This is the start beacon data!",
+    destination: ["shared-storage-select-url"],
+  }
+  let commit_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the commit beacon data!",
+    destination: ["shared-storage-select-url"],
+  }
+  await setupAutomaticBeacon(fencedframe, [start_event, commit_event]);
 
   await actions.pointerMove(0, 0, {origin: fencedframe.element})
                .pointerDown()
                .pointerUp()
                .send();
 
-  const beacon_1_initiator_origin = await nextAutomaticBeacon(
-      "reserved.top_navigation_start", start_beacon_data);
-  assert_equals(beacon_1_initiator_origin, get_host_info().HTTPS_ORIGIN);
-  const beacon_2_initiator_origin = await nextAutomaticBeacon(
-      "reserved.top_navigation_commit", commit_beacon_data);
-  assert_equals(beacon_2_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(start_event.eventType, start_event.eventData);
+  await verifyBeaconData(commit_event.eventType, commit_event.eventData);
 }, 'Set and trigger an automatic beacon in a click handler for SharedStorage');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-clear.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-clear.https.html
index f59fda5..f759c062 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-clear.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-clear.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -17,22 +18,15 @@
   const fencedframe = await attachFencedFrameContext(
       {generator_api: 'fledge', automatic_beacon: true,
        origin: get_host_info().HTTPS_REMOTE_ORIGIN});
-  const new_url = new URL("resources/dummy.html", location.href);
-  const beacon_data = "This is the beacon data!";
-  const beacon_type = "reserved.top_navigation_commit";
 
-  await fencedframe.execute((new_url, beacon_data, beacon_type) => {
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: beacon_data,
-      destination: ["buyer"],
-      once: true,
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-    addEventListener("click", (event) => {
-      window.open(new_url, "_blank");
-    });
-  }, [new_url, beacon_data, beacon_type]);
+  let beacon_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the beacon data!",
+    destination: ["buyer"],
+    once: true,
+  }
+  await setupAutomaticBeacon(fencedframe, [beacon_event],
+      "resources/dummy.html", NavigationTrigger.ClickOnce);
 
   // The first click should trigger the automatic beacon and clear the beacon
   // data.
@@ -40,9 +34,7 @@
                .pointerDown()
                .pointerUp()
                .send();
-  const beacon_initiator_origin =
-      await nextAutomaticBeacon(beacon_type, beacon_data);
-  assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
 
   // The second click should not have any associated automatic beacon info, so
   // no beacon should be sent.
@@ -52,10 +44,8 @@
                .pointerDown()
                .pointerUp()
                .send();
-  const timeout = new Promise(resolve => t.step_timeout(resolve, 1000));
-  const result = await Promise.race(
-      [nextAutomaticBeacon(beacon_type, beacon_data), timeout]);
-  assert_true(typeof result === "undefined");
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
+      t);
 }, 'Set expiring automatic beacon but trigger two events in a click handler');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-persist.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-persist.https.html
index 4da4f89..906a7a0 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-persist.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-two-events-persist.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -17,22 +18,14 @@
   const fencedframe = await attachFencedFrameContext(
       {generator_api: 'fledge', automatic_beacon: true,
        origin: get_host_info().HTTPS_REMOTE_ORIGIN});
-  const new_url = new URL("resources/dummy.html", location.href);
-  const beacon_data = "This is the beacon data!";
-  const beacon_type = "reserved.top_navigation_commit";
+  // `once` defaults to false.
+  let beacon_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the beacon data!",
+    destination: ["buyer"],
+  }
 
-  await fencedframe.execute((new_url, beacon_data, beacon_type) => {
-    // `once` defaults to false.
-    let beacon_event = {
-      eventType: beacon_type,
-      eventData: beacon_data,
-      destination: ["buyer"],
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-    addEventListener("click", (event) => {
-      window.open(new_url, "_blank");
-    });
-  }, [new_url, beacon_data, beacon_type]);
+  await setupAutomaticBeacon(fencedframe, [beacon_event]);
 
   // The first click should trigger the automatic beacon, but the beacon data
   // should not be cleared out.
@@ -40,9 +33,7 @@
                .pointerDown()
                .pointerUp()
                .send();
-  const beacon_1_initiator_origin =
-      await nextAutomaticBeacon(beacon_type, beacon_data);
-  assert_equals(beacon_1_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
 
   // The second click should still have associated automatic beacon data, and a
   // beacon should be sent.
@@ -50,9 +41,7 @@
                .pointerDown()
                .pointerUp()
                .send();
-  const beacon_2_initiator_origin =
-      await nextAutomaticBeacon(beacon_type, beacon_data);
-  assert_equals(beacon_2_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
 }, 'Set persisting automatic beacon but trigger two events in a click handler');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-unfenced-top.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-unfenced-top.https.html
index 945e8092..342e133 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-unfenced-top.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/automatic-beacon-unfenced-top.https.html
@@ -9,6 +9,7 @@
 <script src="/resources/testdriver-actions.js"></script>
 <script src="/resources/testdriver-vendor.js"></script>
 <script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
 
 <body>
 <script>
@@ -27,22 +28,16 @@
   const fencedframe = await attachFencedFrameContext(
     {generator_api: 'fledge', automatic_beacon: true,
      origin: get_host_info().HTTPS_REMOTE_ORIGIN});
-  const new_url = new URL("resources/automatic-beacon-unfenced-page.html",
-      location.href);
 
-  await fencedframe.execute((new_url) => {
-    const beacon_data = "This is the beacon data!";
-    let beacon_event = {
-      eventType: "reserved.top_navigation_commit",
-      eventData: beacon_data,
-      destination: ["buyer"],
-      randomField: "blah",
-    }
-    window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
-    addEventListener("click", (event) => {
-      window.open(new_url, "_unfencedTop");
-    });
-  }, [new_url]);
+  const beacon_event = {
+    eventType: "reserved.top_navigation_commit",
+    eventData: "This is the beacon data!",
+    destination: ["buyer"],
+    randomField: "blah",
+  }
+  await setupAutomaticBeacon(fencedframe, [beacon_event],
+      "resources/automatic-beacon-unfenced-page.html", NavigationTrigger.Click,
+      "_unfencedTop");
 
   await actions.setContext(window)
                .pointerMove(0, 0, {origin: fencedframe.element})
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/resize-lock-input.https.html b/third_party/blink/web_tests/external/wpt/fenced-frame/resize-lock-input.https.html
index 261c9a73..9cee650 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/resize-lock-input.https.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/resize-lock-input.https.html
@@ -14,6 +14,7 @@
       promise_test(async t => {
         const resize_lock_inner_page_is_ready_key = token();
         const resize_lock_resize_is_done_key = token();
+        const resize_lock_frame_requested_after_resize_key = token();
         const resize_lock_report_click_location_key = token();
         const resize_lock_report_click_location_key_after_resize = token();
         const resize_lock_report_click_location_key_after_resize_2 = token();
@@ -22,6 +23,7 @@
             "resources/resize-lock-inner-input.html",
             [resize_lock_inner_page_is_ready_key,
              resize_lock_resize_is_done_key,
+             resize_lock_frame_requested_after_resize_key,
              resize_lock_report_click_location_key,
              resize_lock_report_click_location_key_after_resize,
              resize_lock_report_click_location_key_after_resize_2]));
@@ -39,7 +41,7 @@
 
         let result =
           await nextValueFromServer(resize_lock_report_click_location_key);
-        assert_equals(result, "0,0", "fenced frame event before resize");
+        assert_equals(result, "0,0", "fenced frame event before resize 1");
 
         // The frame should be frozen at 300x150. Resize to create a 2x scale
         // and a horizontal offset of 50px.
@@ -47,6 +49,7 @@
         frame.height = "300";
         writeValueToServer(resize_lock_resize_is_done_key,
                            "outer_page_attempted_resize");
+        await nextValueFromServer(resize_lock_frame_requested_after_resize_key);
 
         // The hit-test data is replicated in the browser and updated
         // asynchronously. Wait to ensure the update has finished.
@@ -78,7 +81,7 @@
                      .pointerUp({sourceName: "finger1"})
                      .send();
             result = await nextValueFromServer(resize_lock_report_click_location_key_after_resize);
-            assert_equals(result, "0,0", "fenced frame event before resize");
+            assert_equals(result, "0,0", "fenced frame event before resize 2");
 
             // Send an event where the bottom left of the scaled frame should
             // render.
@@ -90,7 +93,7 @@
                      .pointerUp({sourceName: "finger1"})
                      .send();
             result = await nextValueFromServer(resize_lock_report_click_location_key_after_resize_2);
-            assert_equals(result, "300,150", "fenced frame event before resize");
+            assert_equals(result, "300,150", "fenced frame event before resize 3");
         }, 1000);
       }, "Test Resize Lock");
     </script>
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/resources/automatic-beacon-helper.js b/third_party/blink/web_tests/external/wpt/fenced-frame/resources/automatic-beacon-helper.js
new file mode 100644
index 0000000..d0a4133
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/resources/automatic-beacon-helper.js
@@ -0,0 +1,104 @@
+// This is a helper file used for the automatic-beacon-*.https.html tests.
+// To use this, make sure you import these scripts:
+// <script src="/resources/testharness.js"></script>
+// <script src="/resources/testharnessreport.js"></script>
+// <script src="/common/utils.js"></script>
+// <script src="/common/dispatcher/dispatcher.js"></script>
+// <script src="resources/utils.js"></script>
+// <script src="/resources/testdriver.js"></script>
+// <script src="/resources/testdriver-actions.js"></script>
+// <script src="/resources/testdriver-vendor.js"></script>
+// <script src="/common/get-host-info.sub.js"></script>
+
+const NavigationTrigger = {
+  Click: 0,
+  ClickOnce: 1,
+  CrossOriginClick: 2,
+  CrossOriginClickNoOptIn: 3
+};
+
+// Registers an automatic beacon in a given remote context frame, and registers
+// the navigation handler for the frame that will trigger the beacon.
+//     remote_context: The context for the fenced frame or URN iframe.
+//      beacon_events: An array of FenceEvents to register with the frame.
+//     navigation_url: The URL the frame will navigate to.
+// navigation_trigger: How the navigation will be performed. Either through a
+//                     click, a click with a `once` event, a click in a
+//                     cross-origin subframe, or a click in a cross-origin
+//                     subframe with no opt-in header.
+//             target: the target of the navigation. Either '_blank' or
+//                     '_unfencedTop'.
+async function setupAutomaticBeacon(
+    remote_context, beacon_events, navigation_url = 'resources/dummy.html',
+    navigation_trigger = NavigationTrigger.Click, target = '_blank') {
+  const full_url = new URL(navigation_url, location.href);
+  await remote_context.execute(
+      async (
+          NavigationTrigger, beacon_events, navigation_trigger, full_url,
+          target) => {
+        switch (navigation_trigger) {
+          case NavigationTrigger.Click:
+            addEventListener('click', (event) => {
+              beacon_events.forEach((beacon_event) => {
+                window.fence.setReportEventDataForAutomaticBeacons(
+                    beacon_event);
+              });
+              window.open(full_url, target);
+            });
+            break;
+          case NavigationTrigger.ClickOnce:
+            beacon_events.forEach((beacon_event) => {
+              window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+            });
+            addEventListener('click', (event) => {
+              window.open(full_url, target);
+            });
+            break;
+          case NavigationTrigger.CrossOriginClick:
+          case NavigationTrigger.CrossOriginClickNoOptIn:
+            beacon_events.forEach((beacon_event) => {
+              window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+            });
+            // Add a cross-origin iframe that will perform the top-level
+            // navigation. Do not set the 'Allow-Fenced-Frame-Automatic-Beacons'
+            // header to true.
+            const iframe = await attachIFrameContext({
+              origin: get_host_info().HTTPS_REMOTE_ORIGIN,
+              headers: [[
+                'Allow-Fenced-Frame-Automatic-Beacons',
+                navigation_trigger == NavigationTrigger.CrossOriginClick ?
+                    'true' :
+                    'false'
+              ]]
+            });
+            await iframe.execute(async (full_url, target) => {
+              addEventListener('click', (event) => {
+                window.open(full_url, target);
+              });
+            }, [full_url, target]);
+            break;
+        }
+      },
+      [NavigationTrigger, beacon_events, navigation_trigger, full_url, target]);
+}
+
+// Checks if an automatic beacon of type `event_type` with contents `event_data`
+// was sent out or not.
+//       event_type: The automatic beacon type to check.
+//       event_data: The automatic beacon data to check.
+// expected_success: Whether we expect the automatic beacon to be sent.
+//                t: The WPT's test object. Only required if
+//                   expected_success = false.
+async function verifyBeaconData(
+    event_type, event_data, expected_success = true, t) {
+  if (expected_success) {
+    const beacon_initiator_origin =
+        await nextAutomaticBeacon(event_type, event_data);
+    assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+  } else {
+    const timeout = new Promise(r => t.step_timeout(r, 1000));
+    const result = await Promise.race(
+        [nextAutomaticBeacon(event_type, event_data), timeout]);
+    assert_true(typeof result === 'undefined');
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/fenced-frame/resources/resize-lock-inner-input.html b/third_party/blink/web_tests/external/wpt/fenced-frame/resources/resize-lock-inner-input.html
index cb17789..5513f89 100644
--- a/third_party/blink/web_tests/external/wpt/fenced-frame/resources/resize-lock-inner-input.html
+++ b/third_party/blink/web_tests/external/wpt/fenced-frame/resources/resize-lock-inner-input.html
@@ -15,6 +15,7 @@
       async function init() {
         const [resize_lock_inner_page_is_ready_key,
                resize_lock_resize_is_done_key,
+               resize_lock_frame_requested_after_resize_key,
                resize_lock_report_click_location_key,
                resize_lock_report_click_location_key_after_resize,
                resize_lock_report_click_location_key_after_resize_2] = parseKeylist();
@@ -25,6 +26,8 @@
           if (eventCount == 1) {
             writeValueToServer(resize_lock_report_click_location_key, point);
             await nextValueFromServer(resize_lock_resize_is_done_key)
+            await new Promise(resolve => requestAnimationFrame(resolve));
+            writeValueToServer(resize_lock_frame_requested_after_resize_key, "ready");
           } else if (eventCount == 2) {
             writeValueToServer(
               resize_lock_report_click_location_key_after_resize, point);
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt
new file mode 100644
index 0000000..3ca95eb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] directionality of bdi elements: no dir attribute empty in rtl parent
+  assert_true: expected true got false
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window.js b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window.js
index 038b3f7..0d4e4b8 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window.js
+++ b/third_party/blink/web_tests/external/wpt/html/dom/elements/global-attributes/dir-assorted.window.js
@@ -80,3 +80,36 @@
   assert_false(e1.matches(":dir(ltr)"), "parent is RTL after changing text in child");
   assert_false(e2.matches(":dir(ltr)"), "child is RTL after changing text in child");
 }, "text changes apply to dir=auto on further ancestor after removing dir=auto from closer ancestor");
+
+for (const bdi_test of [
+  { markup: "<bdi dir=ltr>A</bdi>", expected: "ltr", desc: "dir=ltr with LTR contents" },
+  { markup: "<bdi dir=ltr>\u05d0</bdi>", expected: "ltr", desc: "dir=ltr with RTL contents" },
+  { markup: "<bdi dir=ltr></bdi>", expected: "ltr", desc: "dir=ltr empty" },
+  { markup: "<bdi dir=rtl>A</bdi>", expected: "rtl", desc: "dir=rtl with LTR contents" },
+  { markup: "<bdi dir=rtl>\u05d0</bdi>", expected: "rtl", desc: "dir=rtl with RTL contents" },
+  { markup: "<bdi dir=rtl></bdi>", expected: "rtl", desc: "dir=rtl empty" },
+  { markup: "<bdi dir=auto>A</bdi>", expected: "ltr", desc: "dir=auto with LTR contents" },
+  { markup: "<bdi dir=auto>\u05d0</bdi>", expected: "rtl", desc: "dir=auto with RTL contents" },
+  { markup: "<bdi dir=auto></bdi>", expected: "parent", desc: "dir=auto empty" },
+  { markup: "<bdi>A</bdi>", expected: "ltr", desc: "no dir attribute with LTR contents" },
+  { markup: "<bdi>\u05d0</bdi>", expected: "rtl", desc: "no dir attribute with RTL contents" },
+  { markup: "<bdi></bdi>", expected: "parent", desc: "no dir attribute empty" },
+]) {
+  for (const parent_dir of [ "ltr", "rtl" ]) {
+    test(() => {
+      const parent_element = document.createElement("div");
+      parent_element.dir = parent_dir;
+      document.body.appendChild(parent_element);
+      parent_element.innerHTML = bdi_test.markup;
+      const bdi_element = parent_element.querySelector("bdi");
+      let expected = bdi_test.expected;
+      if (expected == "parent") {
+        expected = parent_dir;
+      }
+      const not_expected = (expected == "ltr") ? "rtl" : "ltr";
+      assert_true(bdi_element.matches(`:dir(${expected})`));
+      assert_false(bdi_element.matches(`:dir(${not_expected})`));
+      parent_element.remove();
+    }, `directionality of bdi elements: ${bdi_test.desc} in ${parent_dir} parent`);
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/resize-observer/multiple-observers-with-mutation-crash.html b/third_party/blink/web_tests/external/wpt/resize-observer/multiple-observers-with-mutation-crash.html
new file mode 100644
index 0000000..c844854e5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/resize-observer/multiple-observers-with-mutation-crash.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html class="test-wait">
+<!--
+  This test is for crbug.com/1368458 which is a crash where we expected
+  up-to-date style&layout when delivering resize observations. We would crash
+  if a resize observer in one frame caused a modification in the presence of a
+  resize observer in another frame.
+-->
+<iframe id="iframe" style="border: none;" srcdoc="
+  <!doctype html>
+  <style>body { margin: 0; }</style>
+  <div id='inner_element'>hello</div>
+  <script>
+    const resizeObserver = new ResizeObserver((entries) => {
+      const size = entries[0].borderBoxSize[0].inlineSize;
+      const event = new CustomEvent('onIframeResizeObserved', {detail: size});
+      parent.document.dispatchEvent(event);
+    });
+    resizeObserver.observe(inner_element);
+  </script>
+"></iframe>
+<div id="outer_element" style="width: 200px;">world</div>
+
+<script>
+  const onInnerElementInitialResize = (event) => {
+    // `inner_element` should result in an initial observation of width 300px.
+    window.document.removeEventListener(
+        'onIframeResizeObserved', onInnerElementInitialResize, false);
+
+    const resizeObserver = new ResizeObserver((entries) => {
+      // `outer_element` should result in an initial observation of width 200px.
+
+      // Modify styles so that inner_element is resized.
+      iframe.contentDocument.body.style.width = "200px";
+    });
+    resizeObserver.observe(outer_element);
+
+    const onInnerElementSecondResize = (event) => {
+      // `inner_element` should result in a second observation of width 100px.
+
+      // Finish the test.
+      document.documentElement.classList.remove('test-wait');
+    };
+    window.document.addEventListener(
+        'onIframeResizeObserved', onInnerElementSecondResize, false);
+  };
+  window.document.addEventListener(
+      'onIframeResizeObserved', onInnerElementInitialResize, false);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
index 364e5d7..3f486cc 100644
--- a/third_party/blink/web_tests/external/wpt/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
+++ b/third_party/blink/web_tests/external/wpt/storage-access-api/resources/storage-access-beyond-cookies-iframe.sub.html
@@ -18,12 +18,24 @@
         if (!!handle.sessionStorage.getItem("test")) {
           message = "Cross-site first-party Session Storage should be empty";
         }
+        handle.sessionStorage.setItem("test2", id);
+        if (window.sessionStorage.getItem("test2") == id) {
+          message = "Handle bound partitioned instead of unpartitioned Session Storage";
+        }
+        handle.sessionStorage.clear();
+        window.sessionStorage.clear();
         break;
       }
       case "localStorage": {
         if (!!handle.localStorage.getItem("test")) {
           message = "Cross-site first-party Local Storage should be empty";
         }
+        handle.localStorage.setItem("test2", id);
+        if (window.localStorage.getItem("test2") == id) {
+          message = "Handle bound partitioned instead of unpartitioned Local Storage";
+        }
+        handle.localStorage.clear();
+        window.localStorage.clear();
         break;
       }
       case "indexedDB": {
diff --git a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
new file mode 100644
index 0000000..ed20109
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
@@ -0,0 +1,5 @@
+This is a testharness.js-based test.
+[FAIL] MediaRecorder returns frames containing video content
+  assert_greater_than: expected a number greater than 1500 but got 1
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-exclusion.js b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-exclusion.js
index 283e441..d33ec0b 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-exclusion.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-exclusion.js
@@ -5,13 +5,15 @@
   await dp.Network.enable();
   await dp.Audits.enable();
 
-  // Set the cookie.
+  // Set the cookie. Make sure the cookie won't be excluded by other reasons
+  // other than EXCLUDE_THIRD_PARTY_PHASEOUT.
   const response = await dp.Network.setCookie({
     url: 'https://example.test:8443',
     secure: true,
     name: 'foo',
     value: 'bar',
     sameSite: 'None',
+    sourcePort: 8443,
   });
 
   if (response.error)
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-warning.js b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-warning.js
index 5a13e6a0..dad3e685 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-warning.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/issues/third-party-cookie-phaseout-warning.js
@@ -5,13 +5,15 @@
     await dp.Network.enable();
     await dp.Audits.enable();
 
-    // Set the cookie.
+    // Set the cookie. Make sure the cookie won't be excluded by other reasons
+    // to be able to have the WARN_THIRD_PARTY_PHASEOUT reason.
     const response = await dp.Network.setCookie({
       url: 'https://example.test:8443',
       secure: true,
       name: 'foo',
       value: 'bar',
       sameSite: 'None',
+      sourcePort: 8443,
     });
 
     if (response.error)
diff --git a/third_party/blink/web_tests/platform/linux/virtual/dir-pseudo-disabled/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/dir-pseudo-disabled/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt
new file mode 100644
index 0000000..f4ddc7a0
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux/virtual/dir-pseudo-disabled/external/wpt/html/dom/elements/global-attributes/dir-assorted.window-expected.txt
@@ -0,0 +1,67 @@
+This is a testharness.js-based test.
+[FAIL] Root element has a direction
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] Element outside the document tree has a direction
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] Non-HTML element outside the document tree has a direction
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] Element without direction has parent element direction
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] Non-HTML element without direction has parent element direction
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] dir inheritance is correct after insertion and removal from document
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] Non-HTML element text contents influence dir=auto
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] text changes apply to dir=auto on further ancestor after removing dir=auto from closer ancestor
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr with LTR contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr with LTR contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr with RTL contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr with RTL contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr empty in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=ltr empty in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl with LTR contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl with LTR contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl with RTL contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl with RTL contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl empty in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=rtl empty in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto with LTR contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto with LTR contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto with RTL contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto with RTL contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto empty in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: dir=auto empty in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute with LTR contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute with LTR contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute with RTL contents in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute with RTL contents in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute empty in ltr parent
+  Failed to execute 'matches' on 'Element': ':dir(ltr)' is not a valid selector.
+[FAIL] directionality of bdi elements: no dir attribute empty in rtl parent
+  Failed to execute 'matches' on 'Element': ':dir(rtl)' is not a valid selector.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/mac/virtual/gpu/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/gpu/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/gpu/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/mac/virtual/oopr-canvas2d/external/wpt/mediacapture-record/MediaRecorder-canvas-media-source.https-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/virtual/boost-font-loading-task-priority/README.md b/third_party/blink/web_tests/virtual/boost-font-loading-task-priority/README.md
new file mode 100644
index 0000000..7bbf9390
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-font-loading-task-priority/README.md
@@ -0,0 +1,4 @@
+This virtual suite runs tests with the BoostFontLoadingTaskPriority
+feature enabled.
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/boost-image-set-loading-task-priority/README.md b/third_party/blink/web_tests/virtual/boost-image-set-loading-task-priority/README.md
new file mode 100644
index 0000000..f0eca47
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-image-set-loading-task-priority/README.md
@@ -0,0 +1,4 @@
+This virtual suite runs tests with the BoostImageSetLoadingTaskPriority
+feature enabled.
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/boost-loading-task-priority/README.md b/third_party/blink/web_tests/virtual/boost-loading-task-priority/README.md
new file mode 100644
index 0000000..167d402e
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-loading-task-priority/README.md
@@ -0,0 +1,8 @@
+This virtual suite runs tests with the following features enabled:
+  BoostImageSetLoadingTaskPriority
+  BoostFontLoadingTaskPriority
+  BoostVideoLoadingTaskPriority
+  BoostNonRenderBlockingStyleLoadingTaskPriority
+  BoostRenderBlockingStyleLoadingTaskPriority
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/boost-non-render-blocking-style-priority/README.md b/third_party/blink/web_tests/virtual/boost-non-render-blocking-style-priority/README.md
new file mode 100644
index 0000000..d52bb7f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-non-render-blocking-style-priority/README.md
@@ -0,0 +1,4 @@
+This virtual suite runs tests with the
+BoostNonRenderBlockingStyleLoadingTaskPriority feature enabled.
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/boost-render-blocking-style-priority/README.md b/third_party/blink/web_tests/virtual/boost-render-blocking-style-priority/README.md
new file mode 100644
index 0000000..f3bb1a6
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-render-blocking-style-priority/README.md
@@ -0,0 +1,4 @@
+This virtual suite runs tests with the
+BoostRenderBlockingStyleLoadingTaskPriority feature enabled.
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/boost-video-loading-task-priority/README.md b/third_party/blink/web_tests/virtual/boost-video-loading-task-priority/README.md
new file mode 100644
index 0000000..dafae0f
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/boost-video-loading-task-priority/README.md
@@ -0,0 +1,4 @@
+This virtual suite runs tests with the BoostVideoLoadingTaskPriority
+feature enabled.
+
+Bug: crbug.com/1470003
\ No newline at end of file
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 4d0ffa0fa..b77f320 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -2043,6 +2043,7 @@
     attribute ZERO
     getter canvas
     getter drawingBufferColorSpace
+    getter drawingBufferFormat
     getter drawingBufferHeight
     getter drawingBufferWidth
     getter unpackColorSpace
@@ -2123,6 +2124,7 @@
     method drawElements
     method drawElementsInstanced
     method drawRangeElements
+    method drawingBufferStorage
     method enable
     method enableVertexAttribArray
     method endQuery
@@ -2483,8 +2485,10 @@
     attribute RGB
     attribute RGB565
     attribute RGB5_A1
+    attribute RGB8
     attribute RGBA
     attribute RGBA4
+    attribute RGBA8
     attribute SAMPLER_2D
     attribute SAMPLER_CUBE
     attribute SAMPLES
@@ -2599,6 +2603,7 @@
     attribute ZERO
     getter canvas
     getter drawingBufferColorSpace
+    getter drawingBufferFormat
     getter drawingBufferHeight
     getter drawingBufferWidth
     getter unpackColorSpace
@@ -2649,6 +2654,7 @@
     method disableVertexAttribArray
     method drawArrays
     method drawElements
+    method drawingBufferStorage
     method enable
     method enableVertexAttribArray
     method finish
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index c8257465..875048e 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -2622,6 +2622,7 @@
 [Worker]     attribute ZERO
 [Worker]     getter canvas
 [Worker]     getter drawingBufferColorSpace
+[Worker]     getter drawingBufferFormat
 [Worker]     getter drawingBufferHeight
 [Worker]     getter drawingBufferWidth
 [Worker]     getter unpackColorSpace
@@ -2702,6 +2703,7 @@
 [Worker]     method drawElements
 [Worker]     method drawElementsInstanced
 [Worker]     method drawRangeElements
+[Worker]     method drawingBufferStorage
 [Worker]     method enable
 [Worker]     method enableVertexAttribArray
 [Worker]     method endQuery
@@ -3062,8 +3064,10 @@
 [Worker]     attribute RGB
 [Worker]     attribute RGB565
 [Worker]     attribute RGB5_A1
+[Worker]     attribute RGB8
 [Worker]     attribute RGBA
 [Worker]     attribute RGBA4
+[Worker]     attribute RGBA8
 [Worker]     attribute SAMPLER_2D
 [Worker]     attribute SAMPLER_CUBE
 [Worker]     attribute SAMPLES
@@ -3178,6 +3182,7 @@
 [Worker]     attribute ZERO
 [Worker]     getter canvas
 [Worker]     getter drawingBufferColorSpace
+[Worker]     getter drawingBufferFormat
 [Worker]     getter drawingBufferHeight
 [Worker]     getter drawingBufferWidth
 [Worker]     getter unpackColorSpace
@@ -3228,6 +3233,7 @@
 [Worker]     method disableVertexAttribArray
 [Worker]     method drawArrays
 [Worker]     method drawElements
+[Worker]     method drawingBufferStorage
 [Worker]     method enable
 [Worker]     method enableVertexAttribArray
 [Worker]     method finish
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 276cd84f..3a0889b 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -9673,6 +9673,7 @@
     attribute ZERO
     getter canvas
     getter drawingBufferColorSpace
+    getter drawingBufferFormat
     getter drawingBufferHeight
     getter drawingBufferWidth
     getter unpackColorSpace
@@ -9753,6 +9754,7 @@
     method drawElements
     method drawElementsInstanced
     method drawRangeElements
+    method drawingBufferStorage
     method enable
     method enableVertexAttribArray
     method endQuery
@@ -10113,8 +10115,10 @@
     attribute RGB
     attribute RGB565
     attribute RGB5_A1
+    attribute RGB8
     attribute RGBA
     attribute RGBA4
+    attribute RGBA8
     attribute SAMPLER_2D
     attribute SAMPLER_CUBE
     attribute SAMPLES
@@ -10229,6 +10233,7 @@
     attribute ZERO
     getter canvas
     getter drawingBufferColorSpace
+    getter drawingBufferFormat
     getter drawingBufferHeight
     getter drawingBufferWidth
     getter unpackColorSpace
@@ -10279,6 +10284,7 @@
     method disableVertexAttribArray
     method drawArrays
     method drawElements
+    method drawingBufferStorage
     method enable
     method enableVertexAttribArray
     method finish
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index 107d6ab..c8caa1f 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1937,6 +1937,7 @@
 [Worker]     attribute ZERO
 [Worker]     getter canvas
 [Worker]     getter drawingBufferColorSpace
+[Worker]     getter drawingBufferFormat
 [Worker]     getter drawingBufferHeight
 [Worker]     getter drawingBufferWidth
 [Worker]     getter unpackColorSpace
@@ -2017,6 +2018,7 @@
 [Worker]     method drawElements
 [Worker]     method drawElementsInstanced
 [Worker]     method drawRangeElements
+[Worker]     method drawingBufferStorage
 [Worker]     method enable
 [Worker]     method enableVertexAttribArray
 [Worker]     method endQuery
@@ -2377,8 +2379,10 @@
 [Worker]     attribute RGB
 [Worker]     attribute RGB565
 [Worker]     attribute RGB5_A1
+[Worker]     attribute RGB8
 [Worker]     attribute RGBA
 [Worker]     attribute RGBA4
+[Worker]     attribute RGBA8
 [Worker]     attribute SAMPLER_2D
 [Worker]     attribute SAMPLER_CUBE
 [Worker]     attribute SAMPLES
@@ -2493,6 +2497,7 @@
 [Worker]     attribute ZERO
 [Worker]     getter canvas
 [Worker]     getter drawingBufferColorSpace
+[Worker]     getter drawingBufferFormat
 [Worker]     getter drawingBufferHeight
 [Worker]     getter drawingBufferWidth
 [Worker]     getter unpackColorSpace
@@ -2543,6 +2548,7 @@
 [Worker]     method disableVertexAttribArray
 [Worker]     method drawArrays
 [Worker]     method drawElements
+[Worker]     method drawingBufferStorage
 [Worker]     method enable
 [Worker]     method enableVertexAttribArray
 [Worker]     method finish
diff --git a/third_party/chromite b/third_party/chromite
index 462c04f..db1410a 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 462c04f706e5185ca5e4e9bede03ec7daa2fc92f
+Subproject commit db1410a8915c656c397f6d9111062c9f5a5947db
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 1e5fa7e..ff1e377 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 1e5fa7e62fdeca99624cc64f84edc865a42cd90f
+Subproject commit ff1e3775275922c5f8dd82af21637e99339997e1
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 6953ebe..cd076ba 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 6953ebe3c1825e0f548e8c124a3b85f6c75dbc73
+Subproject commit cd076ba1b0be061d4446e47f68b3ec53122ce95c
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 7cdb4af..7bc9c8f 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 7cdb4af2d310e71f3dfc36ccbf901f5944abd5c7
+Subproject commit 7bc9c8f33ac40a5b30c4a4f8486f25afde26058b
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 8e82459..d00d10d 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 8e82459cfc5a48ed593407748bbef1e6c37ce943
+Subproject commit d00d10d6f5adcb3c705c4f81b889c59bcb904d28
diff --git a/third_party/nearby/README.chromium b/third_party/nearby/README.chromium
index ef62627e..6d47afb 100644
--- a/third_party/nearby/README.chromium
+++ b/third_party/nearby/README.chromium
@@ -1,7 +1,7 @@
 Name: Nearby Connections Library
 Short Name: Nearby
 URL: https://github.com/google/nearby
-Version: 16cb7f70b3a8b8b04440b7b96534e11df964ca60
+Version: cd4963b0f9c68775372d91dc422c01d703122345
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/nearby/src b/third_party/nearby/src
index 16cb7f7..cd4963b 160000
--- a/third_party/nearby/src
+++ b/third_party/nearby/src
@@ -1 +1 @@
-Subproject commit 16cb7f70b3a8b8b04440b7b96534e11df964ca60
+Subproject commit cd4963b0f9c68775372d91dc422c01d703122345
diff --git a/third_party/skia b/third_party/skia
index 83105a7..0e8023d 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 83105a7fb6e2c2e3dda07a63f5139cbedc8c81cf
+Subproject commit 0e8023dc0a1a5655703b39454c090b5a004415d6
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index d5b9acc..f152619 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -389,7 +389,7 @@
     "includes": [3720],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/customize_chrome/resources.grd": {
-    "META": {"sizes": {"includes": [50],}},
+    "META": {"sizes": {"includes": [55],}},
     "includes": [3740],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/side_panel/history_clusters/resources.grd": {
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 29ef758..d5121c6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -20576,47 +20576,6 @@
   <int value="5" label="Journeys"/>
 </enum>
 
-<enum name="HistoryTopSitesRecoveryEnum">
-  <summary>Error states noted in top_sites_database.cc recovery code.</summary>
-  <int value="0" label="RECOVERY_EVENT_RECOVERED">Successful recovery.</int>
-  <int value="1" label="RECOVERY_EVENT_DEPRECATED">
-    Recovery found deprecated version and razed.
-  </int>
-  <int value="2" label="RECOVERY_EVENT_FAILED_SCOPER (obsolete Mar 2017)">
-    sql::Recovery failed init.
-  </int>
-  <int value="3" label="RECOVERY_EVENT_FAILED_META_VERSION">
-    Failed sql::Recovery::SetupMeta() or GetMetaVersionNumber().
-  </int>
-  <int value="4" label="RECOVERY_EVENT_FAILED_META_WRONG_VERSION">
-    Recovery meta table has an unexpected version.
-  </int>
-  <int value="5" label="RECOVERY_EVENT_FAILED_META_INIT (obsolete Mar 2017)">
-    Failed sql::MetaTable::Init().
-  </int>
-  <int value="6" label="RECOVERY_EVENT_FAILED_SCHEMA_INIT (obsolete Mar 2017)">
-    Failed to init target schema.
-  </int>
-  <int value="7"
-      label="RECOVERY_EVENT_FAILED_AUTORECOVER_THUMBNAILS (obsolete Mar 2017)">
-    Failed recovery on thumbnails table.
-  </int>
-  <int value="8" label="RECOVERY_EVENT_FAILED_COMMIT">
-    Failure from sql::Recovery::Recovered().
-  </int>
-  <int value="9" label="RECOVERY_EVENT_INVARIANT_RANK">
-    Rows were deleted because |url_rank| and |last_forced| didn't agree. Does
-    not prevent recovery.
-  </int>
-  <int value="10" label="RECOVERY_EVENT_INVARIANT_REDIRECT">
-    Rows were deleted because |redirects| did not contain |url|. Does not
-    prevent recovery.
-  </int>
-  <int value="11" label="RECOVERY_EVENT_INVARIANT_CONTIGUOUS">
-    |url_rank| was renumbered due to missing rows. Does not prevent recovery.
-  </int>
-</enum>
-
 <enum name="HomepageLocationType">
   <int value="0" label="POLICY_NTP"/>
   <int value="1" label="POLICY_OTHER"/>
@@ -30641,6 +30600,7 @@
   <int value="508272289" label="SharedHighlightingAmp:disabled"/>
   <int value="510066229"
       label="AutofillEnforceMinRequiredFieldsForQuery:enabled"/>
+  <int value="510759575" label="LeftHandSideActivityIndicators:disabled"/>
   <int value="510814146" label="OfflineBookmarks:enabled"/>
   <int value="510833540" label="UseOutOfProcessVideoDecoding:enabled"/>
   <int value="511179195" label="ShoppingAssist:disabled"/>
@@ -33829,6 +33789,7 @@
   <int value="2001520126" label="AccountManagementFlowsV2:enabled"/>
   <int value="2001562962"
       label="enable-manual-fallback-for-password-saving:enabled"/>
+  <int value="2002003766" label="LeftHandSideActivityIndicators:enabled"/>
   <int value="2002113900" label="AdaptiveButtonInTopToolbar:disabled"/>
   <int value="2002573873" label="ChromeHomeMenuItemsExpandSheet:enabled"/>
   <int value="2002724184" label="MouseAndTrackpadDropdownMenu:enabled"/>
@@ -42654,6 +42615,19 @@
   <int value="4" label="Policy Opt Out"/>
 </enum>
 
+<enum name="RedactionToolCaller">
+  <summary>The caller of the redaction tool.</summary>
+  <int value="1" label="System Logs Uploader"/>
+  <int value="2" label="System Logs Fetcher"/>
+  <int value="3" label="Support Tool"/>
+  <int value="4" label="Error Reporting"/>
+  <int value="5" label="Feedback Tool"/>
+  <int value="6" label="Browser System Logs"/>
+  <int value="7" label="Unit Tests"/>
+  <int value="8" label="Undetermined"/>
+  <int value="9" label="Unknown"/>
+</enum>
+
 <enum name="RelatedActiveContentsSyncAccessInfo">
   <int value="0" label="kNoSyncAccess"/>
   <int value="1"
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index ae3b4f59..f8e17f5 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -1077,16 +1077,6 @@
   <int value="4" label="Stylus"/>
 </enum>
 
-<enum name="IntentUriNavigationResult">
-  <int value="0" label="Intent with an unused fallback URL was launched"/>
-  <int value="1" label="Intent fallback URL was used"/>
-  <int value="2" label="Intent with an unused fallback URL was blocked"/>
-  <int value="3" label="Intent with a fallback URL prompted the user"/>
-  <int value="4" label="Intent without a fallback URL was launched"/>
-  <int value="5" label="Intent without a fallback URL was blocked"/>
-  <int value="6" label="Intent without a fallback URL prompted the user"/>
-</enum>
-
 <enum name="LightTheme">
   <int value="0" label="undefined"/>
   <int value="1" label="no"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index e675658..1ed1ee3 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1756,16 +1756,6 @@
   </summary>
 </histogram>
 
-<histogram name="Android.Intent.IntentUriNavigationResult"
-    enum="IntentUriNavigationResult" expires_after="2023-12-17">
-  <owner>mthiesse@chromium.org</owner>
-  <owner>yfriedman@chromium.org</owner>
-  <summary>
-    Recorded when a navigation to an Intent URI occurs. Records the results of
-    the navigation.
-  </summary>
-</histogram>
-
 <histogram name="Android.Intent.MainFrameIntentLaunch"
     enum="MainFrameIntentLaunch" expires_after="2024-06-30">
   <owner>mthiesse@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 01a3e29..66b293e 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -1934,7 +1934,7 @@
 </histogram>
 
 <histogram name="Ash.CaptureModeController.SwitchesFromInitialCaptureMode"
-    enum="Boolean" expires_after="2024-02-12">
+    enum="Boolean" expires_after="2025-01-08">
   <owner>afakhry@chromium.org</owner>
   <owner>gzadina@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 027256de..7bc2fc8c 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1624,6 +1624,14 @@
     profile remains open. If no domains are visited in a given day, a count of 0
     will be reported for that day.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
@@ -1658,6 +1666,14 @@
     profile remains open. If no domains are visited in a given day, a count of 0
     will be reported for that day.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
@@ -1686,6 +1702,14 @@
     open. If no domains are visited during a 28-day period, a count of 0 will be
     reported for that period.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
@@ -1720,6 +1744,14 @@
     open. If no domains are visited during a 28-day period, a count of 0 will be
     reported for that period.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
@@ -1748,6 +1780,14 @@
     open. If no domains are visited during a 7-day period, a count 0 will be
     reported for that period.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
@@ -1782,6 +1822,14 @@
     open. If no domains are visited during a 7-day period, a count 0 will be
     reported for that period.
 
+    Warning about Android activity types (custom tabs, browser app, etc.): Do
+    not look at this data using an activity type filter; when looking at data
+    from Android, use &quot;Android Chrome (All)&quot;. Why? This histogram
+    reflects all usage of the browser. Using a filter such as &quot;Browser
+    App&quot; is not appropriate because it doesn't make sense to ask &quot;how
+    many web sites were visited in Chrome (in any mode) the last week but only
+    tell me if the answer if this current launch was in browser app mode.&quot;
+
     Warning about delayed data: Chrome may upload logs on a given day without
     uploading this histogram. This can happen because Chrome uploads logs
     initially upon startup. This histogram is emitted shortly _after_ startup.
diff --git a/tools/metrics/histograms/metadata/readaloud/histograms.xml b/tools/metrics/histograms/metadata/readaloud/histograms.xml
index 0ed00dd..16dc7e4 100644
--- a/tools/metrics/histograms/metadata/readaloud/histograms.xml
+++ b/tools/metrics/histograms/metadata/readaloud/histograms.xml
@@ -124,6 +124,31 @@
   </summary>
 </histogram>
 
+<histogram name="ReadAloud.VoiceChanged.{VoiceID}" enum="Boolean"
+    expires_after="2024-06-02">
+  <owner>andreaxg@google.com</owner>
+  <owner>basiaz@google.com</owner>
+  <owner>iwells@chromium.org</owner>
+  <summary>
+    Histogram for recording what voice a user changes to. Emitted when the
+    ReadAloud pref for voice is changed by the user. This only ever logs the
+    value &quot;true&quot; because we only care about the total count.
+  </summary>
+</histogram>
+
+<histogram name="ReadAloud.VoicePreviewed.{VoiceID}" enum="Boolean"
+    expires_after="2024-06-02">
+  <owner>andreaxg@google.com</owner>
+  <owner>basiaz@google.com</owner>
+  <owner>iwells@chromium.org</owner>
+  <summary>
+    Histogram for recording what voice a user previews. Emitted when a voice
+    preview playback is created successfully in ReadAloudController. This only
+    ever logs the value &quot;true&quot; because we only care about the total
+    count.
+  </summary>
+</histogram>
+
 </histograms>
 
 </histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index 08622f6..fb1a3ac 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -100,7 +100,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.DocumentCheckDownloadStats"
-    enum="SBClientDownloadCheckDownloadStats" expires_after="2024-02-20">
+    enum="SBClientDownloadCheckDownloadStats" expires_after="2025-02-20">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -111,7 +111,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.DocumentContainsMacros"
-    enum="BooleanContainsMacros" expires_after="2024-02-20">
+    enum="BooleanContainsMacros" expires_after="2025-02-20">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/win/DebugVisualizers/chrome.natvis b/tools/win/DebugVisualizers/chrome.natvis
index 406d1691..16b33a8 100644
--- a/tools/win/DebugVisualizers/chrome.natvis
+++ b/tools/win/DebugVisualizers/chrome.natvis
@@ -300,4 +300,7 @@
         </ArrayItems>
     </Expand>
   </Type>
+  <Type Name="base::StrongAlias&lt;*,*&gt;">
+    <DisplayString>{value_}</DisplayString>
+  </Type>
 </AutoVisualizer>
diff --git a/ui/base/x/x11_gl_egl_utility.cc b/ui/base/x/x11_gl_egl_utility.cc
index 4ea7dfce..eb56cd68 100644
--- a/ui/base/x/x11_gl_egl_utility.cc
+++ b/ui/base/x/x11_gl_egl_utility.cc
@@ -70,10 +70,4 @@
   }
 }
 
-bool IsTransparentBackgroundSupported() {
-  return x11::Connection::Get()
-      ->GetOrCreateVisualManager()
-      .ArgbVisualAvailable();
-}
-
 }  // namespace ui
diff --git a/ui/base/x/x11_gl_egl_utility.h b/ui/base/x/x11_gl_egl_utility.h
index a57891eb..1a2d9fe 100644
--- a/ui/base/x/x11_gl_egl_utility.h
+++ b/ui/base/x/x11_gl_egl_utility.h
@@ -19,9 +19,6 @@
 void ChoosePlatformCustomAlphaAndBufferSize(EGLint* alpha_size,
                                             EGLint* buffer_size);
 
-// Returns whether transparent background is suppored.
-bool IsTransparentBackgroundSupported();
-
 }  // namespace ui
 
 #endif  // UI_BASE_X_X11_GL_EGL_UTILITY_H_
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
index 376822b..79073ed6b 100644
--- a/ui/base/x/x11_util.cc
+++ b/ui/base/x/x11_util.cc
@@ -46,7 +46,6 @@
 #include "ui/gfx/x/connection.h"
 #include "ui/gfx/x/screensaver.h"
 #include "ui/gfx/x/shm.h"
-#include "ui/gfx/x/visual_manager.h"
 #include "ui/gfx/x/xproto.h"
 
 #if BUILDFLAG(IS_FREEBSD)
@@ -655,19 +654,6 @@
   return false;
 }
 
-bool DoesVisualHaveAlphaForTest() {
-  uint8_t depth = 0;
-  bool visual_has_alpha = false;
-  x11::Connection::Get()->GetOrCreateVisualManager().ChooseVisualForWindow(
-      true, nullptr, &depth, nullptr, &visual_has_alpha);
-
-  if (visual_has_alpha) {
-    DCHECK_EQ(32, depth);
-  }
-
-  return visual_has_alpha;
-}
-
 gfx::ImageSkia GetNativeWindowIcon(intptr_t target_window_id) {
   std::vector<uint32_t> data;
   if (!x11::Connection::Get()->GetArrayProperty(
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
index ab53ab6..24665adf 100644
--- a/ui/base/x/x11_util.h
+++ b/ui/base/x/x11_util.h
@@ -251,9 +251,6 @@
 // Return true if VulkanSurface is supported.
 COMPONENT_EXPORT(UI_BASE_X) bool IsVulkanSurfaceSupported();
 
-// Returns whether ARGB visuals are supported.
-COMPONENT_EXPORT(UI_BASE_X) bool DoesVisualHaveAlphaForTest();
-
 // Returns an icon for a native window referred by |target_window_id|. Can be
 // any window on screen.
 COMPONENT_EXPORT(UI_BASE_X)
diff --git a/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.cc b/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.cc
index b3e92d8..3e84ec8 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.cc
@@ -68,18 +68,10 @@
 void WaylandGLEGLUtility::ChooseEGLAlphaAndBufferSize(EGLint* alpha_size,
                                                       EGLint* buffer_size) {}
 
-bool WaylandGLEGLUtility::IsTransparentBackgroundSupported() const {
-  return true;
-}
-
 void WaylandGLEGLUtility::CollectGpuExtraInfo(
     bool enable_native_gpu_memory_buffers,
     gfx::GpuExtraInfo& gpu_extra_info) const {}
 
-bool WaylandGLEGLUtility::X11DoesVisualHaveAlphaForTest() const {
-  return false;
-}
-
 bool WaylandGLEGLUtility::HasVisualManager() {
   return false;
 }
diff --git a/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.h b/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.h
index b99a5750..7594986 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.h
+++ b/ui/ozone/platform/wayland/gpu/wayland_gl_egl_utility.h
@@ -23,10 +23,8 @@
       std::vector<EGLAttrib>* display_attributes) override;
   void ChooseEGLAlphaAndBufferSize(EGLint* alpha_size,
                                    EGLint* buffer_size) override;
-  bool IsTransparentBackgroundSupported() const override;
   void CollectGpuExtraInfo(bool enable_native_gpu_memory_buffers,
                            gfx::GpuExtraInfo& gpu_extra_info) const override;
-  bool X11DoesVisualHaveAlphaForTest() const override;
   bool HasVisualManager() override;
 };
 
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index a0eef2f..607b4c3b 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -536,11 +536,6 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
-bool WaylandWindow::IsTranslucentWindowOpacitySupported() const {
-  // Wayland compositors always support translucency.
-  return true;
-}
-
 void WaylandWindow::SetDecorationInsets(const gfx::Insets* insets_px) {
   // TODO(crbug.com/1395267): Add window geometry to WaylandWindow::State.
   if ((!frame_insets_px_ && !insets_px) ||
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index d68d4d8..89027ed 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -208,7 +208,6 @@
   gfx::Rect GetRestoredBoundsInDIP() const override;
   bool ShouldWindowContentsBeTransparent() const override;
   void SetAspectRatio(const gfx::SizeF& aspect_ratio) override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   void SetDecorationInsets(const gfx::Insets* insets_px) override;
   void SetWindowIcons(const gfx::ImageSkia& window_icon,
                       const gfx::ImageSkia& app_icon) override;
diff --git a/ui/ozone/platform/x11/gl_egl_utility_x11.cc b/ui/ozone/platform/x11/gl_egl_utility_x11.cc
index 7aded514..3d3d0ab0 100644
--- a/ui/ozone/platform/x11/gl_egl_utility_x11.cc
+++ b/ui/ozone/platform/x11/gl_egl_utility_x11.cc
@@ -27,10 +27,6 @@
   ChoosePlatformCustomAlphaAndBufferSize(alpha_size, buffer_size);
 }
 
-bool GLEGLUtilityX11::IsTransparentBackgroundSupported() const {
-  return ui::IsTransparentBackgroundSupported();
-}
-
 void GLEGLUtilityX11::CollectGpuExtraInfo(
     bool enable_native_gpu_memory_buffers,
     gfx::GpuExtraInfo& gpu_extra_info) const {
@@ -46,10 +42,6 @@
   }
 }
 
-bool GLEGLUtilityX11::X11DoesVisualHaveAlphaForTest() const {
-  return ui::DoesVisualHaveAlphaForTest();
-}
-
 bool GLEGLUtilityX11::HasVisualManager() {
   return true;
 }
diff --git a/ui/ozone/platform/x11/gl_egl_utility_x11.h b/ui/ozone/platform/x11/gl_egl_utility_x11.h
index 2e822203..e6e7b1e 100644
--- a/ui/ozone/platform/x11/gl_egl_utility_x11.h
+++ b/ui/ozone/platform/x11/gl_egl_utility_x11.h
@@ -23,10 +23,8 @@
       std::vector<EGLAttrib>* display_attributes) override;
   void ChooseEGLAlphaAndBufferSize(EGLint* alpha_size,
                                    EGLint* buffer_size) override;
-  bool IsTransparentBackgroundSupported() const override;
   void CollectGpuExtraInfo(bool enable_native_gpu_memory_buffers,
                            gfx::GpuExtraInfo& gpu_extra_info) const override;
-  bool X11DoesVisualHaveAlphaForTest() const override;
   bool HasVisualManager() override;
   absl::optional<base::ScopedEnvironmentVariableOverride>
   MaybeGetScopedDisplayUnsetForVulkan() override;
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index f2221946..443c8177 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -1064,17 +1064,6 @@
   X11Window::UpdateMinAndMaxSize();
 }
 
-bool X11Window::IsTranslucentWindowOpacitySupported() const {
-  // If this function may be called before InitX11Window() (which
-  // initializes |visual_has_alpha_|), return whether it is possible
-  // to create windows with ARGB visuals.
-  if (xwindow_ == x11::Window::None) {
-    connection_->GetOrCreateVisualManager().ArgbVisualAvailable();
-  }
-
-  return visual_has_alpha_;
-}
-
 void X11Window::SetOpacity(float opacity) {
   // X server opacity is in terms of 32 bit unsigned int space, and counts from
   // the opposite direction.
diff --git a/ui/ozone/platform/x11/x11_window.h b/ui/ozone/platform/x11/x11_window.h
index 1d9ba6e..fc999f7 100644
--- a/ui/ozone/platform/x11/x11_window.h
+++ b/ui/ozone/platform/x11/x11_window.h
@@ -116,7 +116,6 @@
   void SetWindowIcons(const gfx::ImageSkia& window_icon,
                       const gfx::ImageSkia& app_icon) override;
   void SizeConstraintsChanged() override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   void SetOpacity(float opacity) override;
   bool CanSetDecorationInsets() const override;
   void SetDecorationInsets(const gfx::Insets* insets_px) override;
diff --git a/ui/ozone/public/platform_gl_egl_utility.h b/ui/ozone/public/platform_gl_egl_utility.h
index 47473203..66fa30b 100644
--- a/ui/ozone/public/platform_gl_egl_utility.h
+++ b/ui/ozone/public/platform_gl_egl_utility.h
@@ -30,19 +30,11 @@
   virtual void ChooseEGLAlphaAndBufferSize(EGLint* alpha_size,
                                            EGLint* buffer_size) = 0;
 
-  // Returns whether the platform supports setting transparent background for
-  // windows.
-  virtual bool IsTransparentBackgroundSupported() const = 0;
-
   // Fills in the platform specific bits of the GPU extra info holder.
   // |enable_native_gpu_memory_buffers| should be taken from GpuPreferences.
   virtual void CollectGpuExtraInfo(bool enable_native_gpu_memory_buffers,
                                    gfx::GpuExtraInfo& gpu_extra_info) const = 0;
 
-  // X11 specific; returns whether the test configuration supports alpha for
-  // window visuals.
-  virtual bool X11DoesVisualHaveAlphaForTest() const = 0;
-
   // X11 specific; returns whether the platform supports visuals.
   virtual bool HasVisualManager();
 
diff --git a/ui/platform_window/platform_window.cc b/ui/platform_window/platform_window.cc
index 89bef01..f34351e 100644
--- a/ui/platform_window/platform_window.cc
+++ b/ui/platform_window/platform_window.cc
@@ -43,10 +43,6 @@
   return false;
 }
 
-bool PlatformWindow::IsTranslucentWindowOpacitySupported() const {
-  return false;
-}
-
 void PlatformWindow::SetOpacity(float opacity) {}
 
 void PlatformWindow::SetVisibilityChangedAnimationsEnabled(bool enabled) {}
diff --git a/ui/platform_window/platform_window.h b/ui/platform_window/platform_window.h
index 845da94..1cf6737 100644
--- a/ui/platform_window/platform_window.h
+++ b/ui/platform_window/platform_window.h
@@ -156,9 +156,6 @@
   // animations.
   virtual bool IsAnimatingClosed() const;
 
-  // Returns true if the window supports translucency.
-  virtual bool IsTranslucentWindowOpacitySupported() const;
-
   // Sets opacity of the platform window.
   virtual void SetOpacity(float opacity);
 
diff --git a/ui/platform_window/win/win_window.cc b/ui/platform_window/win/win_window.cc
index fad36c24..c264c751 100644
--- a/ui/platform_window/win/win_window.cc
+++ b/ui/platform_window/win/win_window.cc
@@ -239,11 +239,6 @@
   return false;
 }
 
-bool WinWindow::IsTranslucentWindowOpacitySupported() const {
-  NOTIMPLEMENTED_LOG_ONCE();
-  return false;
-}
-
 bool WinWindow::IsFullscreen() const {
   return GetPlatformWindowState() == PlatformWindowState::kFullScreen;
 }
diff --git a/ui/platform_window/win/win_window.h b/ui/platform_window/win/win_window.h
index 5ecf188..ed60ad7d 100644
--- a/ui/platform_window/win/win_window.h
+++ b/ui/platform_window/win/win_window.h
@@ -74,7 +74,6 @@
                       const gfx::ImageSkia& app_icon) override;
   void SizeConstraintsChanged() override;
   bool IsAnimatingClosed() const override;
-  bool IsTranslucentWindowOpacitySupported() const override;
 
   bool IsFullscreen() const;
 
diff --git a/ui/views/button_drag_utils.cc b/ui/views/button_drag_utils.cc
index 306962e..1386d96 100644
--- a/ui/views/button_drag_utils.cc
+++ b/ui/views/button_drag_utils.cc
@@ -90,7 +90,7 @@
                        color_provider->GetColor(ui::kColorTextfieldForeground));
 
   SkColor bg_color = color_provider->GetColor(ui::kColorTextfieldBackground);
-  if (drag_widget->IsTranslucentWindowOpacitySupported()) {
+  if (views::Widget::IsWindowCompositingSupported()) {
     button->SetTextShadows(gfx::ShadowValues(
         10, gfx::ShadowValue(gfx::Vector2d(0, 0), 2.0f, bg_color)));
   } else {
diff --git a/ui/views/controls/menu/menu_controller_cocoa_delegate_impl.mm b/ui/views/controls/menu/menu_controller_cocoa_delegate_impl.mm
index 8199063..3be639f 100644
--- a/ui/views/controls/menu/menu_controller_cocoa_delegate_impl.mm
+++ b/ui/views/controls/menu/menu_controller_cocoa_delegate_impl.mm
@@ -173,7 +173,7 @@
     NSSize newTagSize = attachment.image.size;
 
     // The baseline offset of the badge image to the menu text baseline.
-    const int kBadgeBaselineOffset = -4;
+    const int kBadgeBaselineOffset = -3;
     attachment.bounds = NSMakeRect(0, kBadgeBaselineOffset, newTagSize.width,
                                    newTagSize.height);
 
diff --git a/ui/views/controls/textfield/textfield.cc b/ui/views/controls/textfield/textfield.cc
index 57b6f649..81d64b0 100644
--- a/ui/views/controls/textfield/textfield.cc
+++ b/ui/views/controls/textfield/textfield.cc
@@ -195,17 +195,6 @@
   return (ch >= 0x20 && ch < 0x7F) || ch > 0x9F;
 }
 
-bool CanUseTransparentBackgroundForDragImage() {
-#if BUILDFLAG(IS_OZONE)
-  const auto* const egl_utility =
-      ui::OzonePlatform::GetInstance()->GetPlatformGLEGLUtility();
-  return egl_utility ? egl_utility->IsTransparentBackgroundSupported() : false;
-#else
-  // Other platforms allow this.
-  return true;
-#endif
-}
-
 #if BUILDFLAG(IS_MAC)
 const float kAlmostTransparent = 1.0 / 255.0;
 const float kOpaque = 1.0;
@@ -1245,7 +1234,7 @@
 
   SkBitmap bitmap;
   float raster_scale = ScaleFactorForDragFromWidget(GetWidget());
-  SkColor color = CanUseTransparentBackgroundForDragImage()
+  SkColor color = views::Widget::IsWindowCompositingSupported()
                       ? SK_ColorTRANSPARENT
                       : GetBackgroundColor();
   label.Paint(PaintInfo::CreateRootPaintInfo(
diff --git a/ui/views/corewm/tooltip_lacros.cc b/ui/views/corewm/tooltip_lacros.cc
index 380655a..58115595 100644
--- a/ui/views/corewm/tooltip_lacros.cc
+++ b/ui/views/corewm/tooltip_lacros.cc
@@ -76,19 +76,13 @@
   text_ = text;
   position_ = position;
 
-  // TODO(crbug.com/1492220) - Figure out why this is necessary and get the
-  // correctly computed offset.
-  if (parent_window_->GetType() == aura::client::WINDOW_TYPE_POPUP) {
-    position_.Offset(0, 36);
-  }
-
   // Add the distance between `parent_window` and its toplevel window to
   // `position_` since Ash-side server will use this position as relative to
   // wayland toplevel window.
   // TODO(crbug.com/1385219): Use WaylandWindow instead of ToplevelWindow/Popup
   // when it's supported on ozone.
   aura::Window::ConvertPointToTarget(
-      parent_window_, parent_window_->GetToplevelWindow(), &position_);
+      parent_window_, parent_window_->GetRootWindow(), &position_);
   trigger_ = trigger;
 }
 
diff --git a/ui/views/examples/examples_main_proc.cc b/ui/views/examples/examples_main_proc.cc
index 7e2eb57..51a2dc8 100644
--- a/ui/views/examples/examples_main_proc.cc
+++ b/ui/views/examples/examples_main_proc.cc
@@ -156,13 +156,18 @@
   {
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     ExamplesViewsDelegateChromeOS views_delegate;
-#else
+#else  // BUILDFLAG(IS_CHROMEOS_ASH)
     views::DesktopTestViewsDelegate views_delegate;
+#if BUILDFLAG(IS_MAC)
+    views_delegate.set_context_factory(context_factories->GetContextFactory());
+#endif
 #if defined(USE_AURA)
     wm::WMState wm_state;
 #endif
-#endif
-#if BUILDFLAG(ENABLE_DESKTOP_AURA)
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_MAC)
+    display::ScopedNativeScreen desktop_screen;
+#elif BUILDFLAG(ENABLE_DESKTOP_AURA)
     std::unique_ptr<display::Screen> desktop_screen =
         views::CreateDesktopScreen();
 #endif
diff --git a/ui/views/layout/proposed_layout.cc b/ui/views/layout/proposed_layout.cc
index 9d198dd..b8af035 100644
--- a/ui/views/layout/proposed_layout.cc
+++ b/ui/views/layout/proposed_layout.cc
@@ -57,10 +57,6 @@
     default;
 ProposedLayout& ProposedLayout::operator=(ProposedLayout&& other) = default;
 
-bool ProposedLayout::operator==(const ProposedLayout& other) const {
-  return host_size == other.host_size && child_layouts == other.child_layouts;
-}
-
 std::string ProposedLayout::ToString() const {
   std::ostringstream oss;
   oss << "{" << host_size.ToString() << " {";
diff --git a/ui/views/layout/proposed_layout.h b/ui/views/layout/proposed_layout.h
index 2649ed3e..b318f7d 100644
--- a/ui/views/layout/proposed_layout.h
+++ b/ui/views/layout/proposed_layout.h
@@ -23,11 +23,10 @@
   // Note that comparison ignores available size; as two layouts with the same
   // geometry are the same even if the available size is different.
   bool operator==(const ChildLayout& other) const;
-  bool operator!=(const ChildLayout& other) const { return !(*this == other); }
 
   std::string ToString() const;
 
-  raw_ptr<View, AcrossTasksDanglingUntriaged> child_view = nullptr;
+  raw_ptr<View> child_view = nullptr;
   bool visible = false;
   gfx::Rect bounds;
   SizeBounds available_size;
@@ -44,10 +43,7 @@
   ProposedLayout& operator=(const ProposedLayout& other);
   ProposedLayout& operator=(ProposedLayout&& other);
 
-  bool operator==(const ProposedLayout& other) const;
-  bool operator!=(const ProposedLayout& other) const {
-    return !(*this == other);
-  }
+  bool operator==(const ProposedLayout& other) const = default;
 
   std::string ToString() const;
 
diff --git a/ui/views/test/mock_native_widget.h b/ui/views/test/mock_native_widget.h
index 9280c9b..f682604 100644
--- a/ui/views/test/mock_native_widget.h
+++ b/ui/views/test/mock_native_widget.h
@@ -161,7 +161,6 @@
               SetVisibilityAnimationTransition,
               (Widget::VisibilityTransition transition),
               (override));
-  MOCK_METHOD(bool, IsTranslucentWindowOpacitySupported, (), (const override));
   MOCK_METHOD(ui::GestureRecognizer*, GetGestureRecognizer, (), (override));
   MOCK_METHOD(ui::GestureConsumer*, GetGestureConsumer, (), (override));
   MOCK_METHOD(void, OnSizeConstraintsChanged, (), (override));
diff --git a/ui/views/test/views_test_base.cc b/ui/views/test/views_test_base.cc
index bbe9cf8..aa715fd4 100644
--- a/ui/views/test/views_test_base.cc
+++ b/ui/views/test/views_test_base.cc
@@ -30,25 +30,10 @@
 #include "ui/views/widget/native_widget_mac.h"
 #endif
 
-#if BUILDFLAG(IS_OZONE)
-#include "ui/ozone/public/ozone_platform.h"
-#include "ui/ozone/public/platform_gl_egl_utility.h"
-#endif
-
 namespace views {
 
 namespace {
 
-bool DoesVisualHaveAlphaForTest() {
-#if BUILDFLAG(IS_OZONE)
-  const auto* const egl_utility =
-      ui::OzonePlatform::GetInstance()->GetPlatformGLEGLUtility();
-  return egl_utility ? egl_utility->X11DoesVisualHaveAlphaForTest() : false;
-#else
-  return false;
-#endif
-}
-
 }  // namespace
 
 void ViewsTestBase::WidgetCloser::operator()(Widget* widget) const {
@@ -67,8 +52,6 @@
 }
 
 void ViewsTestBase::SetUp() {
-  has_compositing_manager_ = DoesVisualHaveAlphaForTest();
-
   testing::Test::SetUp();
   setup_called_ = true;
 
@@ -135,10 +118,6 @@
   return widget;
 }
 
-bool ViewsTestBase::HasCompositingManager() const {
-  return has_compositing_manager_;
-}
-
 void ViewsTestBase::SimulateNativeDestroy(Widget* widget) {
   test_helper_->SimulateNativeDestroy(widget);
 }
diff --git a/ui/views/test/views_test_base.h b/ui/views/test/views_test_base.h
index 3301b58..c763624 100644
--- a/ui/views/test/views_test_base.h
+++ b/ui/views/test/views_test_base.h
@@ -92,8 +92,6 @@
 
   virtual std::unique_ptr<Widget> CreateTestWidget(Widget::InitParams params);
 
-  bool HasCompositingManager() const;
-
   // Simulate an OS-level destruction of the native window held by non-desktop
   // |widget|.
   void SimulateNativeDestroy(Widget* widget);
@@ -177,7 +175,6 @@
   bool interactive_setup_called_ = false;
   bool setup_called_ = false;
   bool teardown_called_ = false;
-  bool has_compositing_manager_ = false;
 
 #if BUILDFLAG(IS_WIN)
   ui::ScopedOleInitializer ole_initializer_;
diff --git a/ui/views/views_delegate.cc b/ui/views/views_delegate.cc
index 41da8d14..b89b4a2 100644
--- a/ui/views/views_delegate.cc
+++ b/ui/views/views_delegate.cc
@@ -75,6 +75,13 @@
   return true;
 }
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+bool ViewsDelegate::ShouldWindowHaveRoundedCorners(
+    const gfx::NativeWindow window) const {
+  return false;
+}
+#endif
+
 #if BUILDFLAG(IS_WIN)
 HICON ViewsDelegate::GetDefaultWindowIcon() const {
   return nullptr;
diff --git a/ui/views/views_delegate.h b/ui/views/views_delegate.h
index 9d4f2fe..8a94873 100644
--- a/ui/views/views_delegate.h
+++ b/ui/views/views_delegate.h
@@ -129,6 +129,14 @@
   // this returns true.
   virtual bool ShouldCloseMenuIfMouseCaptureLost() const;
 
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Returns true if the native `window` should have rounded corners. The
+  // decision can be based on multiple factors, including the window's current
+  // state.
+  virtual bool ShouldWindowHaveRoundedCorners(
+      const gfx::NativeWindow window) const;
+#endif
+
 #if BUILDFLAG(IS_WIN)
   // Retrieves the default window icon to use for windows if none is specified.
   virtual HICON GetDefaultWindowIcon() const;
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
index 851fe7d9..5babfd2 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc
@@ -1166,11 +1166,6 @@
   wm::SetWindowVisibilityAnimationTransition(content_window_, wm_transition);
 }
 
-bool DesktopNativeWidgetAura::IsTranslucentWindowOpacitySupported() const {
-  return desktop_window_tree_host_ &&
-         desktop_window_tree_host_->IsTranslucentWindowOpacitySupported();
-}
-
 ui::GestureRecognizer* DesktopNativeWidgetAura::GetGestureRecognizer() {
   return aura::Env::GetInstance()->gesture_recognizer();
 }
diff --git a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
index 3ed091c..2946123 100644
--- a/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
+++ b/ui/views/widget/desktop_aura/desktop_native_widget_aura.h
@@ -211,7 +211,6 @@
   void SetVisibilityAnimationDuration(const base::TimeDelta& duration) override;
   void SetVisibilityAnimationTransition(
       Widget::VisibilityTransition transition) override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   ui::GestureRecognizer* GetGestureRecognizer() override;
   ui::GestureConsumer* GetGestureConsumer() override;
   void OnSizeConstraintsChanged() override;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
index fb85354..ea82de3 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host.h
@@ -193,9 +193,6 @@
   // animations.
   virtual bool IsAnimatingClosed() const = 0;
 
-  // Returns true if the Widget supports translucency.
-  virtual bool IsTranslucentWindowOpacitySupported() const = 0;
-
   // Called when the window's size constraints change.
   virtual void SizeConstraintsChanged() = 0;
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.cc
index 5028a156..0c9041e 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.cc
@@ -6,15 +6,20 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 
+#include "base/logging.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_delegate.h"
+#include "ui/compositor/layer.h"
 #include "ui/events/event.h"
 #include "ui/events/event_handler.h"
 #include "ui/events/event_target.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
+#include "ui/gfx/geometry/rrect_f.h"
 #include "ui/ozone/public/ozone_platform.h"
 #include "ui/platform_window/extensions/desk_extension.h"
 #include "ui/platform_window/extensions/pinned_mode_extension.h"
@@ -28,6 +33,7 @@
 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
 #include "ui/views/widget/desktop_aura/window_event_filter_lacros.h"
 #include "ui/views/widget/widget.h"
+#include "ui/views/window/non_client_view.h"
 
 namespace {
 
@@ -141,6 +147,8 @@
   GetContentWindow()->SetProperty(
       chromeos::kWindowStateTypeKey,
       ToChromeosWindowStateType(new_window_show_state));
+
+  UpdateWindowHints();
 }
 
 void DesktopWindowTreeHostLacros::OnFullscreenTypeChanged(
@@ -157,6 +165,13 @@
 void DesktopWindowTreeHostLacros::OnOverviewModeChanged(bool in_overview) {
   GetContentWindow()->SetProperty(chromeos::kIsShowingInOverviewKey,
                                   in_overview);
+
+  // Window corner radius depends on whether the window is in overview mode or
+  // not. Once the overview property has been updated, the browser window
+  // corners need to be updated.
+  // See `chromeos::GetFrameCornerRadius()` for more details.
+  // TODO(b/301501363): Rename to UpdateWindowHints.
+  UpdateWindowHints();
 }
 
 void DesktopWindowTreeHostLacros::OnTooltipShownOnServer(
@@ -174,6 +189,12 @@
   }
 }
 
+void DesktopWindowTreeHostLacros::OnBoundsChanged(const BoundsChange& change) {
+  DesktopWindowTreeHostPlatform::OnBoundsChanged(change);
+
+  UpdateWindowHints();
+}
+
 void DesktopWindowTreeHostLacros::AddAdditionalInitProperties(
     const Widget::InitParams& params,
     ui::PlatformWindowInitProperties* properties) {
@@ -207,6 +228,12 @@
   content_window_observation_.Reset();
 }
 
+void DesktopWindowTreeHostLacros::OnWidgetInitDone() {
+  DesktopWindowTreeHostPlatform::OnWidgetInitDone();
+
+  UpdateWindowHints();
+}
+
 void DesktopWindowTreeHostLacros::CreateNonClientEventFilter() {
   DCHECK(!non_client_window_event_filter_);
   non_client_window_event_filter_ = std::make_unique<WindowEventFilterLacros>(
@@ -217,6 +244,92 @@
   non_client_window_event_filter_.reset();
 }
 
+void DesktopWindowTreeHostLacros::UpdateWindowHints() {
+  if (!GetWidget()->non_client_view()) {
+    return;
+  }
+
+  const float scale = device_scale_factor();
+  const gfx::Size widget_size_px =
+      platform_window()->GetBoundsInPixels().size();
+
+  auto* wayland_extension = ui::GetWaylandExtension(*platform_window());
+
+  const gfx::RoundedCornersF window_radii =
+      wayland_extension ? wayland_extension->GetWindowCornersRadii()
+                        : gfx::RoundedCornersF();
+
+  std::vector<gfx::Rect> opaque_region;
+
+  const bool should_have_rounded_window =
+      views::ViewsDelegate::GetInstance()->ShouldWindowHaveRoundedCorners(
+          GetWidget()->GetNativeWindow());
+
+  if (should_have_rounded_window) {
+    GetContentWindow()->layer()->SetRoundedCornerRadius(window_radii);
+    GetContentWindow()->layer()->SetIsFastRoundedCorner(true);
+
+    // The opaque region is a list of rectangles that contain only fully
+    // opaque pixels of the window.  We need to convert the clipping
+    // rounded-rect into this format.
+    const NonClientFrameView* frame_view =
+        GetWidget()->non_client_view()->frame_view();
+    gfx::Rect local_bounds = frame_view->GetLocalBounds();
+    gfx::RRectF rounded_corners_rect(gfx::RectF(local_bounds), window_radii);
+    gfx::RectF rect_f = rounded_corners_rect.rect();
+    rect_f.Scale(scale);
+
+    // It is acceptable to omit some pixels that are opaque, but the region
+    // must not include any translucent pixels.  Therefore, we must
+    // conservatively scale to the enclosed rectangle.
+    gfx::Rect rect = gfx::ToEnclosedRect(rect_f);
+
+    // Create the initial region from the clipping rectangle without rounded
+    // corners.
+    cc::Region region(rect);
+
+    // Now subtract out the small rectangles that cover the corners.
+    struct {
+      gfx::RRectF::Corner corner;
+      bool left;
+      bool upper;
+    } kCorners[] = {
+        {gfx::RRectF::Corner::kUpperLeft, true, true},
+        {gfx::RRectF::Corner::kUpperRight, false, true},
+        {gfx::RRectF::Corner::kLowerLeft, true, false},
+        {gfx::RRectF::Corner::kLowerRight, false, false},
+    };
+    for (const auto& corner : kCorners) {
+      auto corner_radii = rounded_corners_rect.GetCornerRadii(corner.corner);
+      auto rx = std::ceil(scale * corner_radii.x());
+      auto ry = std::ceil(scale * corner_radii.y());
+      auto corner_rect =
+          gfx::Rect(corner.left ? rect.x() : rect.right() - rx,
+                    corner.upper ? rect.y() : rect.bottom() - ry, rx, ry);
+      region.Subtract(corner_rect);
+    }
+
+    // Convert the region to a list of rectangles.
+    for (gfx::Rect i : region) {
+      opaque_region.push_back(i);
+    }
+  } else {
+    GetContentWindow()->layer()->SetRoundedCornerRadius({});
+    GetContentWindow()->layer()->SetIsFastRoundedCorner(false);
+    opaque_region.push_back({{}, widget_size_px});
+  }
+  // TODO(crbug.com/1306688): Instead of setting OpaqueRegion, set the rounded
+  // corners in dp.
+  platform_window()->SetOpaqueRegion(opaque_region);
+
+  // If the window is rounded, we hint the platform to match the drop shadow's
+  // radii to the window's radii. Otherwise, we allow the platform to
+  // determine the drop shadow's radii.
+  if (should_have_rounded_window) {
+    wayland_extension->SetShadowCornersRadii(window_radii);
+  }
+}
+
 // static
 DesktopWindowTreeHostLacros* DesktopWindowTreeHostLacros::From(
     WindowTreeHost* wth) {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h
index be6d51d..cbb09034 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h
@@ -70,6 +70,7 @@
   void OnTooltipShownOnServer(const std::u16string& text,
                               const gfx::Rect& bounds) override;
   void OnTooltipHiddenOnServer() override;
+  void OnBoundsChanged(const BoundsChange& change) override;
 
   // DesktopWindowTreeHostPlatform overrides:
   void AddAdditionalInitProperties(
@@ -86,6 +87,9 @@
                                intptr_t old) override;
   void OnWindowDestroying(aura::Window* window) override;
 
+  // DesktopWindowTreeHost:
+  void OnWidgetInitDone() override;
+
  private:
   FRIEND_TEST_ALL_PREFIXES(DesktopWindowTreeHostPlatformImplTestWithTouch,
                            HitTest);
@@ -93,6 +97,9 @@
   void CreateNonClientEventFilter();
   void DestroyNonClientEventFilter();
 
+  // Sets hints for the WM/compositor that reflect the rounded corners.
+  void UpdateWindowHints();
+
   // A handler for events intended for non client area.
   // A posthandler for events intended for non client area. Handles events if no
   // other consumer handled them.
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index 23e321b..90ebdc4 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -796,11 +796,6 @@
   return platform_window()->IsAnimatingClosed();
 }
 
-bool DesktopWindowTreeHostPlatform::IsTranslucentWindowOpacitySupported()
-    const {
-  return platform_window()->IsTranslucentWindowOpacitySupported();
-}
-
 void DesktopWindowTreeHostPlatform::SizeConstraintsChanged() {
   platform_window()->SizeConstraintsChanged();
 }
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
index 6c68371..2facd48 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
@@ -133,7 +133,6 @@
   void InitModalType(ui::ModalType modal_type) override;
   void FlashFrame(bool flash_frame) override;
   bool IsAnimatingClosed() const override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   void SizeConstraintsChanged() override;
   bool ShouldUpdateWindowTransparency() const override;
   bool ShouldUseDesktopNativeCursorManager() const override;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 5e613d18..d877d23 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -535,7 +535,7 @@
 }
 
 bool DesktopWindowTreeHostWin::ShouldUseNativeFrame() const {
-  return IsTranslucentWindowOpacitySupported();
+  return true;
 }
 
 bool DesktopWindowTreeHostWin::ShouldWindowContentsBeTransparent() const {
@@ -601,10 +601,6 @@
   return pending_close_;
 }
 
-bool DesktopWindowTreeHostWin::IsTranslucentWindowOpacitySupported() const {
-  return true;
-}
-
 void DesktopWindowTreeHostWin::SizeConstraintsChanged() {
   message_handler_->SizeConstraintsChanged();
 }
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 013f571..e963c86 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -154,7 +154,6 @@
   void InitModalType(ui::ModalType modal_type) override;
   void FlashFrame(bool flash_frame) override;
   bool IsAnimatingClosed() const override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   void SizeConstraintsChanged() override;
   bool ShouldUpdateWindowTransparency() const override;
   bool ShouldUseDesktopNativeCursorManager() const override;
diff --git a/ui/views/widget/native_widget_aura.cc b/ui/views/widget/native_widget_aura.cc
index 19f417b..d328226 100644
--- a/ui/views/widget/native_widget_aura.cc
+++ b/ui/views/widget/native_widget_aura.cc
@@ -977,10 +977,6 @@
   wm::SetWindowVisibilityAnimationTransition(window_, wm_transition);
 }
 
-bool NativeWidgetAura::IsTranslucentWindowOpacitySupported() const {
-  return true;
-}
-
 ui::GestureRecognizer* NativeWidgetAura::GetGestureRecognizer() {
   return aura::Env::GetInstance()->gesture_recognizer();
 }
diff --git a/ui/views/widget/native_widget_aura.h b/ui/views/widget/native_widget_aura.h
index 73ddbe3..5deaa7cb 100644
--- a/ui/views/widget/native_widget_aura.h
+++ b/ui/views/widget/native_widget_aura.h
@@ -164,7 +164,6 @@
   void SetVisibilityAnimationDuration(const base::TimeDelta& duration) override;
   void SetVisibilityAnimationTransition(
       Widget::VisibilityTransition transition) override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   ui::GestureRecognizer* GetGestureRecognizer() override;
   ui::GestureConsumer* GetGestureConsumer() override;
   void OnSizeConstraintsChanged() override;
diff --git a/ui/views/widget/native_widget_mac.h b/ui/views/widget/native_widget_mac.h
index f82654a..dd20417 100644
--- a/ui/views/widget/native_widget_mac.h
+++ b/ui/views/widget/native_widget_mac.h
@@ -202,7 +202,6 @@
   void SetVisibilityAnimationDuration(const base::TimeDelta& duration) override;
   void SetVisibilityAnimationTransition(
       Widget::VisibilityTransition transition) override;
-  bool IsTranslucentWindowOpacitySupported() const override;
   ui::GestureRecognizer* GetGestureRecognizer() override;
   ui::GestureConsumer* GetGestureConsumer() override;
   void OnSizeConstraintsChanged() override;
diff --git a/ui/views/widget/native_widget_mac.mm b/ui/views/widget/native_widget_mac.mm
index 1ae9331..3a6617a 100644
--- a/ui/views/widget/native_widget_mac.mm
+++ b/ui/views/widget/native_widget_mac.mm
@@ -896,10 +896,6 @@
     GetNSWindowMojo()->SetTransitionsToAnimate(transitions);
 }
 
-bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const {
-  return false;
-}
-
 ui::GestureRecognizer* NativeWidgetMac::GetGestureRecognizer() {
   static base::NoDestructor<ui::GestureRecognizerImplMac> recognizer;
   return recognizer.get();
diff --git a/ui/views/widget/native_widget_private.h b/ui/views/widget/native_widget_private.h
index 23af442..3ac7a45a 100644
--- a/ui/views/widget/native_widget_private.h
+++ b/ui/views/widget/native_widget_private.h
@@ -242,7 +242,6 @@
       const base::TimeDelta& duration) = 0;
   virtual void SetVisibilityAnimationTransition(
       Widget::VisibilityTransition transition) = 0;
-  virtual bool IsTranslucentWindowOpacitySupported() const = 0;
   virtual ui::GestureRecognizer* GetGestureRecognizer() = 0;
   virtual ui::GestureConsumer* GetGestureConsumer() = 0;
   virtual void OnSizeConstraintsChanged() = 0;
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index 866bb701..5093dee2 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -58,6 +58,10 @@
 #include "ui/linux/linux_ui.h"
 #endif
 
+#if BUILDFLAG(IS_OZONE)
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
 namespace views {
 
 namespace {
@@ -358,6 +362,17 @@
   return type == InitParams::TYPE_WINDOW || type == InitParams::TYPE_BUBBLE;
 }
 
+// static
+bool Widget::IsWindowCompositingSupported() {
+#if BUILDFLAG(IS_WIN)
+  return true;
+#elif BUILDFLAG(IS_OZONE)
+  return ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported();
+#else
+  return false;
+#endif
+}
+
 void Widget::Init(InitParams params) {
   TRACE_EVENT0("views", "Widget::Init");
 
@@ -1322,11 +1337,6 @@
   root_view_->OnMouseMoved(mouse_event);
 }
 
-bool Widget::IsTranslucentWindowOpacitySupported() const {
-  return native_widget_ ? native_widget_->IsTranslucentWindowOpacitySupported()
-                        : false;
-}
-
 ui::GestureRecognizer* Widget::GetGestureRecognizer() {
   return native_widget_ ? native_widget_->GetGestureRecognizer() : nullptr;
 }
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index 65959ee..1939476 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -279,8 +279,7 @@
     // If kOpaque, we can perform optimizations based on the widget being fully
     // opaque. Default is based on ViewsDelegate::GetOpacityForInitParams().
     // Defaults to kOpaque for non-window widgets. Translucent windows may not
-    // always be supported. Use IsTranslucentWindowOpacitySupported() to
-    // determine whether they are.
+    // always be supported.
     WindowOpacity opacity = WindowOpacity::kInferred;
 
     bool accept_events = true;
@@ -558,6 +557,8 @@
   // Returns true if the specified type requires a NonClientView.
   static bool RequiresNonClientView(InitParams::Type type);
 
+  static bool IsWindowCompositingSupported();
+
   // Initializes the widget, and in turn, the native widget. |params| should be
   // moved to Init() by the caller.
   void Init(InitParams params);
@@ -1059,9 +1060,6 @@
   // mouse location to refresh hovering status in the widget.
   void SynthesizeMouseMoveEvent();
 
-  // Whether the widget supports translucency.
-  bool IsTranslucentWindowOpacitySupported() const;
-
   // Returns the gesture recognizer which can handle touch/gesture events on
   // this.
   ui::GestureRecognizer* GetGestureRecognizer();
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc
index f0a4dd4..7a8b781 100644
--- a/ui/views/widget/widget_unittest.cc
+++ b/ui/views/widget/widget_unittest.cc
@@ -94,11 +94,6 @@
 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
 #endif
 
-#if BUILDFLAG(IS_OZONE)
-#include "ui/ozone/public/ozone_platform.h"
-#include "ui/ozone/public/platform_gl_egl_utility.h"
-#endif
-
 namespace views::test {
 
 namespace {
@@ -1687,11 +1682,6 @@
   widget()->IsStackedAbove(other_widget->GetNativeView());
 }
 
-TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
-       IsTranslucentWindowOpacitySupported) {
-  widget()->IsTranslucentWindowOpacitySupported();
-}
-
 TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsVisible) {
   widget()->IsVisible();
 }
@@ -5931,28 +5921,6 @@
 
 namespace {
 
-bool CanHaveCompositingManager() {
-#if BUILDFLAG(IS_OZONE)
-  auto* const egl_utility =
-      ui::OzonePlatform::GetInstance()->GetPlatformGLEGLUtility();
-  return (egl_utility != nullptr) && egl_utility->HasVisualManager();
-#else
-  return false;
-#endif
-}
-
-bool ExpectWidgetTransparency(Widget::InitParams::WindowOpacity opacity) {
-  switch (opacity) {
-    case Widget::InitParams::WindowOpacity::kOpaque:
-      return false;
-    case Widget::InitParams::WindowOpacity::kTranslucent:
-      return true;
-    case Widget::InitParams::WindowOpacity::kInferred:
-      ADD_FAILURE() << "WidgetOpacity must be explicitly set";
-      return false;
-  }
-}
-
 class CompositingWidgetTest : public DesktopWidgetTest {
  public:
   CompositingWidgetTest()
@@ -6011,13 +5979,6 @@
 
       EXPECT_EQ(IsNativeWindowTransparent(widget->GetNativeWindow()),
                 should_be_transparent);
-
-      if (CanHaveCompositingManager()) {
-        if (HasCompositingManager() && ExpectWidgetTransparency(opacity))
-          EXPECT_TRUE(widget->IsTranslucentWindowOpacitySupported());
-        else
-          EXPECT_FALSE(widget->IsTranslucentWindowOpacitySupported());
-      }
     }
   }
 
diff --git a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
index a6934e4..ad5d4e8 100644
--- a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
+++ b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
@@ -103,6 +103,7 @@
 <dialog id="dialog">
   <div id="header">
     <h2 id="title">[[i18n('hueSliderTitle')]]</h2>
+    <slot name="headerSuffix"></slot>
     <cr-icon-button id="close" class="icon-clear"
         aria-label$="[[i18n('close')]]"
         title$="[[i18n('close')]]"
diff --git a/v8 b/v8
index 017bc6b..13036d7 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 017bc6b893e733863245628de6f216922150bed3
+Subproject commit 13036d7a479a2da10ead4bdc99804cd2e93bb201